import asyncio
import json
import time

from channels.routing import URLRouter
from channels.testing import ChannelsLiveServerTestCase, WebsocketCommunicator
from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from django.contrib.staticfiles.testing import StaticLiveServerTestCase
from django.test import tag
from django.urls import reverse
from selenium import webdriver
from selenium.webdriver import Chrome, Firefox, FirefoxOptions
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait

from course_flow.models.project import Project
from course_flow.models.relations.liveProjectUser import LiveProjectUser
from course_flow.models.relations.outcomeHorizontalLink import (
    OutcomeHorizontalLink,
)
from course_flow.models.relations.outcomeNode import OutcomeNode
from course_flow.models.relations.outcomeOutcome import OutcomeOutcome
from course_flow.models.relations.outcomeWorkflow import OutcomeWorkflow
from course_flow.models.relations.workflowProject import WorkflowProject
from course_flow.routing import websocket_urlpatterns
from course_flow.utils import get_model_from_str

from ...models.activity import Activity
from ...models.course import Course
from ...models.courseFlowUser import CourseFlowUser
from ...models.discipline import Discipline
from ...models.favourite import Favourite
from ...models.liveAssignment import LiveAssignment
from ...models.liveProject import LiveProject
from ...models.objectPermission import ObjectPermission
from ...models.outcome import Outcome
from ...models.program import Program
from ...models.userAssignment import UserAssignment
from ...models.workflow import Workflow
from .utils import get_author, login

timeout = 10


class ChannelsStaticLiveServerTestCase(ChannelsLiveServerTestCase):
    serve_static = True


class SeleniumBase:
    def __init__(self):
        self.selenium = None
        self.executable_path: str = "nopath"

    def create_ff_browser(self):
        options = FirefoxOptions()
        options.add_argument("--headless")
        options.add_argument("--disable-extensions")
        options.add_argument("--no-sandbox")
        options.add_argument("--ignore-certificate-errors")
        options.add_argument("--allow-running-insecure-content")
        options.add_argument("window-size=1920x1480")
        options.set_capability("acceptInsecureCerts", True)
        browser = Firefox(options=options)
        browser.implicitly_wait(10)
        return browser

    def create_chrome_browser(self, options):
        if options is None:
            options = webdriver.chrome.options.Options()

        if self.test_headless:
            options.add_argument("--headless")
        options.add_argument("--disable-extensions")
        options.add_argument("--no-sandbox")
        options.add_argument("--ignore-certificate-errors")
        options.add_argument("--disable-gpu")
        options.add_argument("--allow-running-insecure-content")
        options.set_capability("acceptInsecureCerts", True)
        if self.executable_path == "nopath":
            browser = Chrome(options=options)
        else:
            browser = Chrome(self.executable_path, options=options)
        return browser

    def init_selenium(self, options: dict = None):
        if settings.CHROMEDRIVER_PATH is not None:
            self.executable_path = settings.CHROMEDRIVER_PATH

        try:
            self.test_headless = settings.COURSEFLOW_TEST_HEADLESS
        except AttributeError:
            self.test_headless = False
        try:
            if settings.COURSEFLOW_TEST_BROWSER == "ff":
                return self.create_ff_browser()
        except AttributeError:
            pass
        return self.create_chrome_browser(options)


@tag("selenium")
class SeleniumRegistrationTestCase(StaticLiveServerTestCase):
    def setUp(self):
        selbase = SeleniumBase()
        self.selenium = selbase.init_selenium()

        super().setUp()

    def tearDown(self):
        self.selenium.quit()
        super().tearDown()

    def test_register_user(self):
        print("\nIn method", self._testMethodName, ": ")
        selenium = self.selenium

        selenium.get(self.live_server_url + "/register/")

        first_name = selenium.find_element(By.ID, "id_first_name")
        last_name = selenium.find_element(By.ID, "id_last_name")
        username = selenium.find_element(By.ID, "id_username")
        email = selenium.find_element(By.ID, "id_email")
        password1 = selenium.find_element(By.ID, "id_password1")
        password2 = selenium.find_element(By.ID, "id_password2")

        username_text = "test_user1"
        password_text = "testpass123"

        first_name.send_keys("test")
        last_name.send_keys("user")
        username.send_keys(username_text)
        email.send_keys("testuser@test.com")
        password1.send_keys(password_text)
        password2.send_keys(password_text)

        selenium.find_element(By.ID, "register-button").click()
        time.sleep(100)
        self.assertEqual(
            self.live_server_url + "/course-flow/home/", selenium.current_url
        )


class SeleniumUserTestCase(ChannelsStaticLiveServerTestCase):
    def setUp(self):
        selbase = SeleniumBase()
        self.selenium = selbase.init_selenium()

        super().setUp()
        selenium = self.selenium
        selenium.maximize_window()

        self.user = login(self)
        self.user.first_name = "old first"
        self.user.last_name = "old last"
        self.user.save()
        selenium.get(self.live_server_url + reverse("course_flow:home"))
        username = selenium.find_element(By.ID, "id_username")
        password = selenium.find_element(By.ID, "id_password")
        username.send_keys("testuser1")
        password.send_keys("testpass1")
        selenium.find_element(By.CSS_SELECTOR, "button[type=Submit]").click()

    def tearDown(self):
        self.selenium.quit()
        super().tearDown()

    def test_edit_user(self):
        print("\nIn method", self._testMethodName, ": ")
        selenium = self.selenium
        wait = WebDriverWait(selenium, timeout=10)
        selenium.get(self.live_server_url + reverse("course_flow:user-update"))

        courseflow_user = CourseFlowUser.objects.get(pk=self.user.pk)
        assert courseflow_user.first_name == "old first"
        assert courseflow_user.last_name == "old last"
        first_name = selenium.find_element(By.ID, "id_first_name")
        last_name = selenium.find_element(By.ID, "id_last_name")
        first_name.clear()
        last_name.clear()

        new_first = "new first"
        new_last = "new last"

        first_name.send_keys(new_first)
        last_name.send_keys(new_last)
        selenium.find_element(By.ID, "save-button").click()

        time.sleep(1)

        courseflow_user = CourseFlowUser.objects.get(pk=self.user.pk)

        assert courseflow_user.first_name == "new first"
        assert courseflow_user.last_name == "new last"


class SeleniumLiveProjectTestCase(ChannelsStaticLiveServerTestCase):
    def setUp(self):
        selbase = SeleniumBase()
        self.selenium = selbase.init_selenium()

        super().setUp()
        selenium = self.selenium
        selenium.maximize_window()

        self.user = login(self)
        selenium.get(self.live_server_url + reverse("course_flow:home"))
        username = selenium.find_element(By.ID, "id_username")
        password = selenium.find_element(By.ID, "id_password")
        username.send_keys("testuser1")
        password.send_keys("testpass1")
        selenium.find_element(By.CSS_SELECTOR, "button[type=Submit]").click()

    def tearDown(self):
        self.selenium.quit()
        super().tearDown()

    def test_create_liveproject(self):
        print("\nIn method", self._testMethodName, ": ")
        selenium = self.selenium
        wait = WebDriverWait(selenium, timeout=10)
        project = Project.objects.create(author=self.user, title="new title")
        selenium.get(
            self.live_server_url
            + reverse("course_flow:project-update", args=[project.pk])
        )
        time.sleep(1)
        selenium.find_element(By.ID, "overflow-options").click()
        selenium.find_element(By.ID, "live-project").click()
        alert = wait.until(expected_conditions.alert_is_present())
        selenium.switch_to.alert.accept()
        time.sleep(1)
        assert (
            "new title"
            in selenium.find_element(By.CSS_SELECTOR, ".project-title").text
        )
        assert LiveProject.objects.filter(project=project).count() == 1

    def test_my_classrooms_teacher(self):
        print("\nIn method", self._testMethodName, ": ")
        selenium = self.selenium
        wait = WebDriverWait(selenium, timeout=10)
        project = Project.objects.create(author=self.user, title="new title")
        LiveProject.objects.create(project=project)
        selenium.get(self.live_server_url + "/course-flow/home/")
        button_workflows = wait.until(
            EC.element_to_be_clickable((By.ID, "panel-my-live-projects"))
        )
        selenium.find_element_by_css_selector(
            "#panel-my-live-projects"
        ).click()
        selenium.find_element(By.CSS_SELECTOR, ".workflow-top-row a").click()
        # windows = selenium.window_handles
        # selenium.switch_to_window(windows[0])
        # selenium.close()
        # selenium.switch_to_window(windows[1])
        assert (
            "new title"
            in selenium.find_element(By.CSS_SELECTOR, ".project-title").text
        )
        selenium.get(self.live_server_url + "/course-flow/mylibrary/")
        time.sleep(0.5)
        selenium.find_element(
            By.CSS_SELECTOR, ".workflow-for-menu .workflow-live-classroom"
        ).click()
        time.sleep(0.5)
        # windows = selenium.window_handles
        # selenium.switch_to_window(windows[0])
        # selenium.close()
        # selenium.switch_to_window(windows[1])
        assert (
            "new title"
            in selenium.find_element(By.CSS_SELECTOR, ".project-title").text
        )

    def test_my_classrooms_student(self):
        print("\nIn method", self._testMethodName, ": ")
        self.user.groups.remove(self.user.groups.first())
        author = get_author()
        selenium = self.selenium
        wait = WebDriverWait(selenium, timeout=10)
        project = Project.objects.create(author=author, title="new title")
        liveproject = LiveProject.objects.create(project=project)
        LiveProjectUser.objects.create(
            liveproject=liveproject,
            user=self.user,
            role_type=LiveProjectUser.ROLE_STUDENT,
        )
        selenium.get(self.live_server_url + "/course-flow/home/")
        # make sure only correct items are visible
        button_workflows = wait.until(
            EC.element_to_be_clickable((By.ID, "panel-my-live-projects"))
        )
        assert (
            len(selenium.find_elements(By.CSS_SELECTOR, "#panel-my-projects"))
            == 0
        )
        time.sleep(0.5)
        selenium.find_element(
            By.CSS_SELECTOR, "#panel-my-live-projects"
        ).click()
        selenium.find_element(By.CSS_SELECTOR, ".workflow-top-row a").click()
        # windows = selenium.window_handles
        # selenium.switch_to_window(windows[0])
        # selenium.close()
        # selenium.switch_to_window(windows[1])
        assert (
            "new title"
            in selenium.find_element(By.CSS_SELECTOR, ".workflow-title").text
        )
        selenium.get(self.live_server_url + "/course-flow/home/")
        selenium.find_element(
            By.CSS_SELECTOR, ".home-item .home-title-row a"
        ).click()
        selenium.find_element(By.CSS_SELECTOR, ".workflow-top-row a").click()
        # windows = selenium.window_handles
        # selenium.switch_to_window(windows[0])
        # selenium.close()
        # selenium.switch_to_window(windows[1])
        assert (
            "new title"
            in selenium.find_element(By.CSS_SELECTOR, ".workflow-title").text
        )
        selenium.get(self.live_server_url + "/course-flow/home/")
        time.sleep(0.5)
        selenium.find_element(By.CSS_SELECTOR, ".workflow-top-row a").click()
        assert (
            "new title"
            in selenium.find_element(By.CSS_SELECTOR, ".workflow-title").text
        )

    def test_settings(self):
        print("\nIn method", self._testMethodName, ": ")
        selenium = self.selenium
        wait = WebDriverWait(selenium, timeout=10)
        project = Project.objects.create(author=self.user, title="new title")
        liveproject = LiveProject.objects.create(project=project)
        selenium.get(
            self.live_server_url
            + reverse("course_flow:project-update", args=[project.id])
        )
        selenium.find_element(By.CSS_SELECTOR, "#edit-project-button").click()
        time.sleep(1)
        selenium.find_element(By.ID, "default-assign-to-all").click()
        selenium.find_element(By.ID, "default-self-reporting").click()
        selenium.find_element(By.ID, "default-single-completion").click()
        selenium.find_element(By.ID, "default-all-workflows-visible").click()

        selenium.find_element(
            By.CSS_SELECTOR, ".workflow-details button"
        ).click()

        time.sleep(1)
        liveproject = LiveProject.objects.first()
        self.assertEqual(liveproject.default_self_reporting, False)
        self.assertEqual(liveproject.default_assign_to_all, False)
        self.assertEqual(liveproject.default_single_completion, True)
        self.assertEqual(liveproject.default_all_workflows_visible, True)

    def test_add_roles(self):
        print("\nIn method", self._testMethodName, ": ")
        selenium = self.selenium
        wait = WebDriverWait(selenium, timeout=10)
        project = Project.objects.create(author=self.user, title="new title")

        user2 = get_author()
        liveproject = LiveProject.objects.create(project=project)
        selenium.get(
            self.live_server_url
            + reverse("course_flow:project-update", args=[project.id])
        )
        selenium.find_element(By.CSS_SELECTOR, "#button_students").click()
        time.sleep(1)
        inputs = selenium.find_elements(By.CSS_SELECTOR, ".user-add input")
        inputs[0].send_keys("testuser2")
        time.sleep(2)
        selenium.find_elements(By.CSS_SELECTOR, ".ui-autocomplete li")[
            0
        ].click()
        time.sleep(0.5)
        selenium.find_elements(By.CSS_SELECTOR, ".user-add button")[0].click()
        time.sleep(2)
        self.assertEqual(
            LiveProjectUser.objects.filter(
                user=user2,
                role_type=LiveProjectUser.ROLE_STUDENT,
                liveproject=liveproject,
            ).count(),
            1,
        )

        selenium.find_elements(By.CSS_SELECTOR, ".user-label select")[
            1
        ].click()
        selenium.find_elements(By.CSS_SELECTOR, ".user-label select option")[
            4
        ].click()
        time.sleep(2)
        self.assertEqual(
            LiveProjectUser.objects.filter(
                user=user2,
                role_type=LiveProjectUser.ROLE_STUDENT,
                liveproject=liveproject,
            ).count(),
            0,
        )
        self.assertEqual(
            LiveProjectUser.objects.filter(
                user=user2,
                role_type=LiveProjectUser.ROLE_TEACHER,
                liveproject=liveproject,
            ).count(),
            1,
        )
        selenium.find_elements(By.CSS_SELECTOR, ".user-label select")[
            1
        ].click()
        selenium.find_elements(By.CSS_SELECTOR, ".user-label select option")[
            5
        ].click()
        alert = wait.until(expected_conditions.alert_is_present())
        selenium.switch_to.alert.accept()
        time.sleep(2)
        self.assertEqual(
            LiveProjectUser.objects.filter(
                user=user2,
                liveproject=liveproject,
            ).count(),
            0,
        )

    def test_enroll_from_link(self):
        self.user.groups.remove(self.user.groups.first())
        author = get_author()
        selenium = self.selenium
        wait = WebDriverWait(selenium, timeout=10)
        project = Project.objects.create(author=author, title="new title")
        liveproject = LiveProject.objects.create(project=project)
        selenium.get(
            self.live_server_url
            + reverse(
                "course_flow:register-as-student",
                args=[project.registration_hash()],
            )
        )
        assert (
            "new title"
            in selenium.find_element(By.CSS_SELECTOR, ".workflow-title").text
        )

    def test_add_workflows(self):
        print("\nIn method", self._testMethodName, ": ")
        selenium = self.selenium
        wait = WebDriverWait(selenium, timeout=10)

        # create test data, project + activity
        project = Project.objects.create(author=self.user, title="new title")
        workflow = Activity.objects.create(
            author=self.user, title="new workflow"
        )
        WorkflowProject.objects.create(project=project, workflow=workflow)
        liveproject = LiveProject.objects.create(project=project)

        # navigate to just created project single page
        print(reverse("course_flow:project-update", args=[project.id]))
        selenium.get(
            self.live_server_url
            + reverse("course_flow:project-update", args=[project.id])
        )
        # todo where is this from?
        print(selenium.current_url)
        button_workflows = wait.until(
            EC.element_to_be_clickable((By.ID, "button_workflows"))
        )
        button_workflows.click()

        wait.until(
            EC.element_to_be_clickable(
                (By.CSS_SELECTOR, ".permission-select select ")
            )
        )
        self.assertEqual(
            len(
                selenium.find_elements(
                    By.CSS_SELECTOR,
                    ".permission-select select option:checked[value='true']",
                )
            ),
            0,
        )
        self.assertEqual(
            len(
                selenium.find_elements(
                    By.CSS_SELECTOR,
                    ".permission-select select option:checked[value='false']",
                )
            ),
            1,
        )

        selenium.find_element(
            By.CSS_SELECTOR, ".permission-select select"
        ).click()
        selenium.find_elements(
            By.CSS_SELECTOR, ".permission-select select option"
        )[1].click()
        time.sleep(2)
        self.assertEqual(liveproject.visible_workflows.count(), 1)
        self.assertEqual(
            len(
                selenium.find_elements(
                    By.CSS_SELECTOR,
                    ".permission-select select option:checked[value='true']",
                )
            ),
            1,
        )
        self.assertEqual(
            len(
                selenium.find_elements(
                    By.CSS_SELECTOR,
                    ".permission-select select option:checked[value='false']",
                )
            ),
            0,
        )

        selenium.find_element(
            By.CSS_SELECTOR, ".permission-select select"
        ).click()
        selenium.find_elements(
            By.CSS_SELECTOR, ".permission-select select option"
        )[0].click()
        time.sleep(2)
        self.assertEqual(liveproject.visible_workflows.count(), 0)
        self.assertEqual(
            len(
                selenium.find_elements(
                    By.CSS_SELECTOR,
                    ".permission-select select option:checked[value='true']",
                )
            ),
            0,
        )
        self.assertEqual(
            len(
                selenium.find_elements(
                    By.CSS_SELECTOR,
                    ".permission-select select option:checked[value='false']",
                )
            ),
            1,
        )

    def test_student_workflows(self):
        print("\nIn method", self._testMethodName, ": ")
        selenium = self.selenium
        wait = WebDriverWait(selenium, timeout=10)
        user2 = get_author()
        project = Project.objects.create(author=user2, title="new title")
        workflow = Activity.objects.create(author=user2, title="new workflow")
        WorkflowProject.objects.create(project=project, workflow=workflow)
        liveproject = LiveProject.objects.create(project=project)
        LiveProjectUser.objects.create(
            liveproject=liveproject,
            user=self.user,
            role_type=LiveProjectUser.ROLE_STUDENT,
        )
        liveproject.visible_workflows.add(workflow)
        selenium.get(
            self.live_server_url
            + reverse("course_flow:live-project-update", args=[project.id])
        )
        selenium.find_element(By.CSS_SELECTOR, "#button_workflows").click()
        time.sleep(1)

        self.assertEqual(
            len(
                selenium.find_elements(By.CSS_SELECTOR, ".menu-grid")[
                    0
                ].find_elements(By.CSS_SELECTOR, ".workflow-for-menu")
            ),
            1,
        )

        selenium.find_element(
            By.CSS_SELECTOR, ".menu-grid .workflow-for-menu .workflow-title"
        ).click()

        # windows = selenium.window_handles
        # selenium.switch_to_window(windows[0])
        # selenium.close()
        # selenium.switch_to_window(windows[1])

        time.sleep(1)
        assert (
            "new workflow"
            in selenium.find_element(By.CSS_SELECTOR, ".project-title").text
        )

    def test_create_assignment(self):
        print("\nIn method", self._testMethodName, ": ")
        selenium = self.selenium
        wait = WebDriverWait(selenium, timeout=10)
        project = Project.objects.create(author=self.user, title="new title")
        workflow = Course.objects.create(
            author=self.user, title="new workflow"
        )
        node = workflow.weeks.first().nodes.create(
            author=self.user, column=workflow.columns.first()
        )
        WorkflowProject.objects.create(project=project, workflow=workflow)
        liveproject = LiveProject.objects.create(project=project)
        liveproject.visible_workflows.add(workflow)
        selenium.get(
            self.live_server_url
            + reverse("course_flow:project-update", args=[project.id])
        )
        time.sleep(1)
        selenium.find_element(By.CSS_SELECTOR, "#button_assignments").click()
        time.sleep(3)

        selenium.find_element(
            By.CSS_SELECTOR, "#select-workflow > div"
        ).click()

        time.sleep(3)

        hover_item = selenium.find_element(By.CSS_SELECTOR, ".node")
        click_item = selenium.find_element(
            By.CSS_SELECTOR, ".node .mouseover-actions img"
        )
        action_hover_click(selenium, hover_item, click_item).perform()

        time.sleep(3)

        selenium.find_elements(By.CSS_SELECTOR, "#users_all option")[0].click()
        selenium.find_element(By.ID, "add-user").click()

        time.sleep(2)

        self.assertEqual(
            len(
                selenium.find_elements(By.CSS_SELECTOR, "#users_chosen option")
            ),
            1,
        )
        self.assertEqual(
            len(selenium.find_elements(By.CSS_SELECTOR, "#users_all option")),
            0,
        )
        self.assertEqual(
            UserAssignment.objects.filter(
                user=self.user,
                assignment=liveproject.liveassignment_set.all().first(),
            ).count(),
            1,
        )

        selenium.find_element(By.ID, "button_report").click()
        time.sleep(2)
        selenium.find_element(By.CSS_SELECTOR, "td > input").click()
        time.sleep(1)
        self.assertEqual(UserAssignment.objects.first().completed, True)

    def test_student_assignment(self):
        print("\nIn method", self._testMethodName, ": ")
        selenium = self.selenium
        wait = WebDriverWait(selenium, timeout=10)
        user2 = get_author()
        project = Project.objects.create(author=user2, title="new title")
        workflow = Course.objects.create(author=user2, title="new workflow")
        workflow2 = Activity.objects.create(
            author=user2, title="linked workflow"
        )
        WorkflowProject.objects.create(project=project, workflow=workflow)
        WorkflowProject.objects.create(project=project, workflow=workflow2)
        node = workflow.weeks.first().nodes.create(
            author=self.user, column=workflow.columns.first()
        )
        node.linked_workflow = workflow2
        node.save()
        liveproject = LiveProject.objects.create(
            project=project, default_assign_to_all=False
        )
        LiveProjectUser.objects.create(
            liveproject=liveproject,
            user=self.user,
            role_type=LiveProjectUser.ROLE_STUDENT,
        )
        assignment = LiveAssignment.objects.create(
            author=user2, task=node, liveproject=liveproject
        )

        selenium.get(
            self.live_server_url
            + reverse("course_flow:live-project-update", args=[project.id])
        )
        selenium.find_element(By.CSS_SELECTOR, "#button_assignments").click()
        time.sleep(2)

        self.assertEqual(
            len(
                selenium.find_elements(
                    By.CSS_SELECTOR, ".workflow-details .node"
                )
            ),
            0,
        )

        UserAssignment.objects.create(user=self.user, assignment=assignment)

        selenium.get(
            self.live_server_url
            + reverse("course_flow:live-project-update", args=[project.id])
        )
        selenium.find_element(By.CSS_SELECTOR, "#button_assignments").click()
        time.sleep(1)

        self.assertEqual(
            len(
                selenium.find_elements(
                    By.CSS_SELECTOR, ".workflow-details .node"
                )
            ),
            1,
        )

        selenium.find_element(
            By.CSS_SELECTOR, ".node input[type='checkbox']"
        ).click()

        time.sleep(1)

        self.assertEqual(
            UserAssignment.objects.get(user=self.user).completed, True
        )

        self.assertEqual(
            len(
                selenium.find_elements(
                    By.CSS_SELECTOR, ".node .linked-workflow"
                )
            ),
            0,
        )

        liveproject.visible_workflows.add(workflow)
        liveproject.visible_workflows.add(workflow2)

        selenium.get(
            self.live_server_url
            + reverse("course_flow:live-project-update", args=[project.id])
        )
        selenium.find_element(By.CSS_SELECTOR, "#button_assignments").click()
        time.sleep(2)

        self.assertEqual(
            len(
                selenium.find_elements(
                    By.CSS_SELECTOR, ".node .linked-workflow"
                )
            ),
            2,
        )
        selenium.find_element(
            By.CSS_SELECTOR, ".node .containing-workflow"
        ).click()
        time.sleep(2)

        windows = selenium.window_handles
        selenium.switch_to.window(windows[1])

        self.assertEqual(
            "new workflow",
            selenium.find_element(By.CSS_SELECTOR, ".project-title").text,
        )

        selenium.close()
        selenium.switch_to.window(windows[0])

        selenium.get(
            self.live_server_url
            + reverse("course_flow:live-project-update", args=[project.id])
        )
        selenium.find_element(By.CSS_SELECTOR, "#button_assignments").click()
        time.sleep(2)

        self.assertEqual(
            len(
                selenium.find_elements(
                    By.CSS_SELECTOR, ".node .linked-workflow"
                )
            ),
            2,
        )
        selenium.find_element(
            By.CSS_SELECTOR, ".node .linked-workflow:not(.containing-workflow)"
        ).click()
        time.sleep(2)
        windows = selenium.window_handles
        selenium.switch_to.window(windows[1])
        self.assertEqual(
            "linked workflow",
            selenium.find_element(By.CSS_SELECTOR, ".project-title").text,
        )

    def test_create_assignment_from_workflow(self):
        print("\nIn method", self._testMethodName, ": ")
        selenium = self.selenium
        wait = WebDriverWait(selenium, timeout=10)
        project = Project.objects.create(author=self.user, title="new title")
        workflow = Activity.objects.create(
            author=self.user, title="new workflow"
        )
        node = workflow.weeks.first().nodes.create(
            author=self.user, column=workflow.columns.first()
        )
        WorkflowProject.objects.create(project=project, workflow=workflow)
        liveproject = LiveProject.objects.create(project=project)
        liveproject.visible_workflows.add(workflow)
        selenium.get(
            self.live_server_url
            + reverse("course_flow:workflow-update", args=[workflow.id])
        )
        time.sleep(2)

        hover_item = selenium.find_element(
            By.CSS_SELECTOR, ".workflow-details .node"
        )
        click_item = selenium.find_element(
            By.CSS_SELECTOR, ".node .assignment-button img"
        )
        action_hover_click(selenium, hover_item, click_item).perform()

        selenium.find_element(
            By.CSS_SELECTOR, ".node .create-assignment img"
        ).click()
        time.sleep(1)

        self.assertEqual(LiveAssignment.objects.filter(task=node).count(), 1)
        self.assertEqual(
            len(
                selenium.find_elements(By.CSS_SELECTOR, ".assignment-in-node")
            ),
            1,
        )

    def test_student_complete_assignment_from_workflow(self):
        print("\nIn method", self._testMethodName, ": ")
        user2 = get_author()
        selenium = self.selenium
        wait = WebDriverWait(selenium, timeout=10)
        project = Project.objects.create(author=user2, title="new title")
        workflow = Activity.objects.create(author=user2, title="new workflow")
        node = workflow.weeks.first().nodes.create(
            author=user2, column=workflow.columns.first()
        )
        WorkflowProject.objects.create(project=project, workflow=workflow)
        liveproject = LiveProject.objects.create(
            project=project, default_assign_to_all=False
        )
        liveproject.visible_workflows.add(workflow)
        LiveProjectUser.objects.create(
            user=self.user,
            liveproject=liveproject,
            role_type=LiveProjectUser.ROLE_STUDENT,
        )
        assignment = LiveAssignment.objects.create(
            liveproject=liveproject, task=node
        )
        selenium.get(
            self.live_server_url
            + reverse("course_flow:workflow-update", args=[workflow.id])
        )
        time.sleep(2)

        hover_item = selenium.find_element(
            By.CSS_SELECTOR, ".workflow-details .node"
        )
        click_item = selenium.find_element(
            By.CSS_SELECTOR, ".node .assignment-button img"
        )
        action_hover_click(selenium, hover_item, click_item).perform()

        time.sleep(1)
        self.assertEqual(
            len(
                selenium.find_elements(By.CSS_SELECTOR, ".assignment-in-node")
            ),
            0,
        )
        UserAssignment.objects.create(user=self.user, assignment=assignment)

        selenium.find_element(
            By.CSS_SELECTOR, ".node .close-button img"
        ).click()
        time.sleep(0.5)

        action_hover_click(selenium, hover_item, click_item).perform()
        time.sleep(2)
        self.assertEqual(
            len(
                selenium.find_elements(By.CSS_SELECTOR, ".assignment-in-node")
            ),
            1,
        )
        selenium.find_element(
            By.CSS_SELECTOR, ".assignment-timing input[type='checkbox']"
        ).click()
        time.sleep(1)
        self.assertEqual(
            UserAssignment.objects.filter(user=self.user).first().completed,
            True,
        )
        selenium.find_element(
            By.CSS_SELECTOR, ".assignment-timing input[type='checkbox']"
        ).click()
        time.sleep(1)
        self.assertEqual(
            UserAssignment.objects.filter(user=self.user).first().completed,
            False,
        )


class SeleniumFrenchTestCase(ChannelsStaticLiveServerTestCase):
    def setUp(self):
        chrome_options = webdriver.chrome.options.Options()
        chrome_options.add_experimental_option(
            "prefs", {"intl.accept_languages": "fr"}
        )
        chrome_options.add_argument("--lang=fr")
        selbase = SeleniumBase()
        self.selenium = selbase.init_selenium(chrome_options)

        super().setUp()
        selenium = self.selenium
        selenium.maximize_window()

        self.user = login(self)
        selenium.get(self.live_server_url + "/course-flow/home/")
        username = selenium.find_element(By.ID, "id_username")
        password = selenium.find_element(By.ID, "id_password")
        username.send_keys("testuser1")
        password.send_keys("testpass1")
        selenium.find_element(By.CSS_SELECTOR, "button[type=Submit]").click()

    def tearDown(self):
        self.selenium.quit()
        super().tearDown()

    def test_home(self):
        print("\nIn method", self._testMethodName, ": ")
        selenium = self.selenium
        selenium.get(self.live_server_url + "/course-flow/home/")
        assert (
            "Projets récents"
            in selenium.find_elements(By.CSS_SELECTOR, ".home-item-title")[
                0
            ].text
        )


class SeleniumWorkflowsTestCase(ChannelsStaticLiveServerTestCase):
    def setUp(self):
        selbase = SeleniumBase()
        self.selenium = selbase.init_selenium()

        super().setUp()
        selenium = self.selenium
        selenium.maximize_window()

        self.user = login(self)
        selenium.get(self.live_server_url + "/course-flow/home/")
        username = selenium.find_element(By.ID, "id_username")
        password = selenium.find_element(By.ID, "id_password")
        username.send_keys("testuser1")
        password.send_keys("testpass1")
        selenium.find_element(By.CSS_SELECTOR, "button[type=Submit]").click()

    def tearDown(self):
        self.selenium.quit()
        super().tearDown()

    def test_create_project_and_workflows(self):
        print("\nIn method", self._testMethodName, ": ")
        selenium = self.selenium
        wait = WebDriverWait(selenium, timeout=10)

        # why were we going home first?
        # selenium.get(self.live_server_url + "/course-flow/home/")

        # Navigate to My Library
        selenium.get(self.live_server_url + "/course-flow/mylibrary/")

        create_project_button = wait.until(
            EC.element_to_be_clickable((By.ID, "create-project-button"))
        )
        create_project_button.click()

        # click new project button
        selenium.find_element(
            By.CSS_SELECTOR, "#project-create-library"
        ).click()

        title = selenium.find_element(By.ID, "id_title")
        description = selenium.find_element(By.ID, "id_description")
        project_title = "test project title"
        project_description = "test project description"
        title.send_keys(project_title)
        description.send_keys(project_description)
        selenium.find_element(By.ID, "save-button").click()

        # this sleep is a hack because jquery is giving back interrmittent errors on save and we can't figure out why yet
        time.sleep(1)
        # wait for evidence that the save is finished
        wait.until(
            EC.presence_of_element_located((By.CSS_SELECTOR, ".project-menu"))
        )
        project_url = selenium.current_url

        assert (
            project_title
            in selenium.find_element(By.CSS_SELECTOR, ".project-title").text
        )

        assert (
            project_description
            in selenium.find_element(
                By.CSS_SELECTOR, ".project-description"
            ).text
        )

        # input('wait')
        # Create templates
        selenium.get(self.live_server_url + "/course-flow/mylibrary/")
        wait.until(
            EC.presence_of_element_located((By.CSS_SELECTOR, ".project-menu"))
        )
        mylibrary = selenium.current_url

        for template_type in ["activity", "course"]:
            print("in: " + template_type)

            create_project_button = wait.until(
                EC.element_to_be_clickable((By.ID, "create-project-button"))
            )
            create_project_button.click()

            # click activity-strategy-create
            # click course-strategy-create
            selenium.find_element(
                By.CSS_SELECTOR, "#" + template_type + "-strategy-create"
            ).click()

            title = wait.until(
                EC.presence_of_element_located((By.ID, "id_title"))
            )

            description = selenium.find_element(By.ID, "id_description")
            project_title = "test project title"
            project_description = "test project description"
            title.send_keys(project_title)
            description.send_keys(project_description)

            selenium.find_element(By.ID, "save-button").click()
            # this sleep is a hack because jquery is giving back intermittent errors on save and we can't figure out why yet
            time.sleep(1)
            assert (
                project_title
                in selenium.find_element(
                    By.CSS_SELECTOR, ".project-title"
                ).text
            )
            assert (
                project_description
                in selenium.find_element(
                    By.CSS_SELECTOR, ".project-description"
                ).text
            )

            selenium.get(mylibrary)

        for i, workflow_type in enumerate(["activity", "course", "program"]):
            selenium.get(project_url)
            time.sleep(1)
            # Create the workflow
            create_project_button = wait.until(
                EC.element_to_be_clickable((By.ID, "create-project-button"))
            )
            create_project_button.click()

            create_workflow_button = wait.until(
                EC.element_to_be_clickable(
                    (By.ID, workflow_type + "-create-project")
                )
            )
            create_workflow_button.click()

            title = selenium.find_element(By.ID, "id_title")
            description = selenium.find_element(By.ID, "id_description")
            project_title = "test " + workflow_type + " title"
            project_description = "test " + workflow_type + " description"
            title.send_keys(project_title)
            description.send_keys(project_description)
            selenium.find_element(By.ID, "save-button").click()
            # hack, jquery gives error otherwise
            time.sleep(2)

            assert (
                project_title
                in selenium.find_element(
                    By.CSS_SELECTOR, ".project-title"
                ).text
            )
            assert (
                project_description
                in selenium.find_element(
                    By.CSS_SELECTOR, ".project-description"
                ).text
            )
            selenium.get(project_url)

            # edit link
            time.sleep(1)
            selenium.find_element(
                By.CSS_SELECTOR,
                ".workflow-for-menu." + workflow_type + " .workflow-title",
            ).click()

            time.sleep(5)

            assert (
                project_title
                in selenium.find_element(
                    By.CSS_SELECTOR, ".project-title"
                ).text
            )

            selenium.find_element(By.CSS_SELECTOR, "#overflow-options").click()
            selenium.find_element(
                By.CSS_SELECTOR, "#copy-to-project-button"
            ).click()
            time.sleep(5)
            assert (
                project_title
                in selenium.find_element(
                    By.CSS_SELECTOR, ".project-title"
                ).text
            )
            self.assertEqual(
                get_model_from_str(workflow_type)
                .objects.exclude(parent_workflow=None)
                .count(),
                1,
            )
            selenium.find_element(By.CSS_SELECTOR, "#overflow-options").click()
            selenium.find_element(By.CSS_SELECTOR, "#delete-workflow").click()
            wait.until(expected_conditions.alert_is_present())
            selenium.switch_to.alert.accept()
            time.sleep(2)
            self.assertEqual(
                get_model_from_str(workflow_type)
                .objects.filter(is_strategy=False, deleted=False)
                .count(),
                1,
            )
            self.assertEqual(
                get_model_from_str(workflow_type)
                .objects.filter(is_strategy=False, deleted=True)
                .count(),
                1,
            )

    def test_edit_project_details(self):
        print("\nIn method", self._testMethodName, ": ")
        selenium = self.selenium
        wait = WebDriverWait(selenium, timeout=10)
        project = Project.objects.create(author=self.user)
        discipline = Discipline.objects.create(title="discipline")
        selenium.get(
            self.live_server_url
            + reverse("course_flow:project-update", args=[project.pk])
        )
        time.sleep(1)
        selenium.find_element(By.ID, "edit-project-button").click()
        selenium.find_element(By.ID, "project-title-input").send_keys(
            "new title"
        )
        selenium.find_element(By.ID, "project-description-input").send_keys(
            "new description"
        )
        selenium.find_element(By.ID, "project-discipline-input").click()
        selenium.find_elements(By.CSS_SELECTOR, ".ui-autocomplete li")[
            0
        ].click()
        selenium.find_element(By.ID, "save-changes").click()
        assert (
            "new title"
            in selenium.find_element(By.CSS_SELECTOR, ".project-title").text
        )
        assert (
            "new description"
            in selenium.find_element(
                By.CSS_SELECTOR, ".project-description"
            ).text
        )
        time.sleep(2)
        project = Project.objects.first()
        self.assertEqual(project.title, "new title")
        self.assertEqual(project.description, "new description")
        self.assertEqual(project.disciplines.first(), discipline)

    def test_import_favourite(self):
        print("\nIn method", self._testMethodName, ": ")
        selenium = self.selenium
        wait = WebDriverWait(selenium, timeout=10)
        author = get_author()
        project = Project.objects.create(
            author=author, published=True, title="published project"
        )
        WorkflowProject.objects.create(
            workflow=Activity.objects.create(author=author, published=True),
            project=project,
        )
        WorkflowProject.objects.create(
            workflow=Course.objects.create(author=author, published=True),
            project=project,
        )
        WorkflowProject.objects.create(
            workflow=Program.objects.create(author=author, published=True),
            project=project,
        )
        ObjectPermission.objects.create(
            user=self.user,
            content_object=project,
            permission_type=ObjectPermission.PERMISSION_VIEW,
        )
        Favourite.objects.create(user=self.user, content_object=project)
        Favourite.objects.create(
            user=self.user,
            content_object=Workflow.objects.get(
                pk=Activity.objects.first().pk
            ),
        )
        Favourite.objects.create(
            user=self.user,
            content_object=Workflow.objects.get(pk=Course.objects.first().pk),
        )
        Favourite.objects.create(
            user=self.user,
            content_object=Workflow.objects.get(pk=Program.objects.first().pk),
        )

        # View the favourites
        selenium.get(
            self.live_server_url + reverse("course_flow:my-favourites")
        )
        time.sleep(2)
        self.assertEqual(
            len(
                selenium.find_elements(
                    By.CSS_SELECTOR,
                    ".workflow-for-menu .workflow-toggle-favourite .filled",
                )
            ),
            4,
        )
        selenium.find_element(
            By.CSS_SELECTOR, ".workflow-for-menu.project .workflow-title"
        ).click()
        favourites = selenium.current_url
        time.sleep(2)
        self.assertEqual(
            len(
                selenium.find_elements(
                    By.CSS_SELECTOR,
                    ".workflow-for-menu .workflow-toggle-favourite .filled",
                )
            ),
            3,
        )
        # Import the project
        selenium.find_element(By.CSS_SELECTOR, "#overflow-options").click()
        selenium.find_element(By.CSS_SELECTOR, "#copy-button").click()

        time.sleep(2)
        new_project = Project.objects.get(parent_project=project)
        for workflow_type in ["activity", "course", "program"]:
            assert WorkflowProject.objects.get(
                workflow=get_model_from_str(workflow_type).objects.get(
                    author=self.user,
                    parent_workflow=get_model_from_str(
                        workflow_type
                    ).objects.get(author=author),
                ),
                project=new_project,
            )

        # Create a project, then import the favourites one at a time
        my_project1 = Project.objects.create(author=self.user)
        selenium.get(favourites)
        time.sleep(1)
        selenium.find_element(
            By.CSS_SELECTOR, ".workflow-for-menu.activity .workflow-title"
        ).click()
        time.sleep(0.5)
        selenium.find_element(By.CSS_SELECTOR, "#overflow-options").click()
        selenium.find_element(By.CSS_SELECTOR, "#copy-button").click()
        time.sleep(0.5)
        selenium.find_elements(
            By.CSS_SELECTOR, "#popup-container #tabs-0 .workflow-for-menu"
        )[1].click()
        selenium.find_element(By.CSS_SELECTOR, "#set-linked-workflow").click()
        time.sleep(1)

        selenium.get(favourites)
        time.sleep(1)
        selenium.find_element(
            By.CSS_SELECTOR, ".workflow-for-menu.course .workflow-title"
        ).click()
        time.sleep(0.5)
        selenium.find_element(By.CSS_SELECTOR, "#overflow-options").click()
        selenium.find_element(By.CSS_SELECTOR, "#copy-button").click()
        time.sleep(0.5)
        selenium.find_elements(
            By.CSS_SELECTOR, "#popup-container #tabs-0 .workflow-for-menu"
        )[1].click()
        selenium.find_element(By.CSS_SELECTOR, "#set-linked-workflow").click()
        time.sleep(1)

        selenium.get(favourites)
        time.sleep(1)
        selenium.find_element(
            By.CSS_SELECTOR, ".workflow-for-menu.program .workflow-title"
        ).click()
        time.sleep(0.5)
        selenium.find_element(By.CSS_SELECTOR, "#overflow-options").click()
        selenium.find_element(By.CSS_SELECTOR, "#copy-button").click()
        time.sleep(0.5)
        selenium.find_elements(
            By.CSS_SELECTOR, "#popup-container #tabs-0 .workflow-for-menu"
        )[1].click()
        selenium.find_element(By.CSS_SELECTOR, "#set-linked-workflow").click()
        time.sleep(1)

        for workflow_type in ["activity", "course", "program"]:
            assert WorkflowProject.objects.get(
                workflow=get_model_from_str(workflow_type)
                .objects.filter(
                    author=self.user,
                    parent_workflow=get_model_from_str(
                        workflow_type
                    ).objects.get(author=author),
                )
                .last(),
                project=my_project1,
            )

    def test_workflow_read_only(self):
        print("\nIn method", self._testMethodName, ": ")
        selenium = self.selenium
        wait = WebDriverWait(selenium, timeout=10)
        author = get_author()
        project = Project.objects.create(author=author, published=True)
        for workflow_type in ["activity", "course", "program"]:
            workflow = get_model_from_str(workflow_type).objects.create(
                author=author
            )
            WorkflowProject.objects.create(workflow=workflow, project=project)
            workflow.weeks.first().nodes.create(
                author=self.user, column=workflow.columns.first()
            )

            selenium.get(
                self.live_server_url
                + reverse("course_flow:workflow-update", args=[workflow.pk])
            )
            time.sleep(2)

            self.assertEqual(
                len(selenium.find_elements(By.CSS_SELECTOR, ".action-button")),
                0,
            )

            selenium.find_elements(By.CSS_SELECTOR, ".week")[0].click()
            time.sleep(0.3)
            self.assertEqual(
                len(
                    selenium.find_elements(
                        By.CSS_SELECTOR,
                        "#edit-menu .right-panel-inner #title-editor:disabled",
                    )
                ),
                1,
            )

    def test_workflow_editing(self):
        print("\nIn method", self._testMethodName, ": ")
        selenium = self.selenium
        wait = WebDriverWait(selenium, timeout=10)
        project = Project.objects.create(author=self.user)
        for workflow_type in ["activity", "course", "program"]:
            workflow = get_model_from_str(workflow_type).objects.create(
                author=self.user
            )
            WorkflowProject.objects.create(workflow=workflow, project=project)
            workflow.weeks.first().nodes.create(
                author=self.user, column=workflow.columns.first()
            )
            selenium.get(
                self.live_server_url
                + reverse("course_flow:workflow-update", args=[workflow.pk])
            )
            time.sleep(2)
            num_columns = workflow.columns.all().count()
            num_weeks = workflow.weeks.all().count()
            num_nodes = 1
            self.assertEqual(
                len(
                    selenium.find_elements(
                        By.CSS_SELECTOR, ".workflow-details .column"
                    )
                ),
                num_columns,
            )
            self.assertEqual(
                len(
                    selenium.find_elements(
                        By.CSS_SELECTOR, ".workflow-details .week"
                    )
                ),
                num_weeks,
            )
            self.assertEqual(
                len(
                    selenium.find_elements(
                        By.CSS_SELECTOR, ".workflow-details .node"
                    )
                ),
                num_nodes,
            )
            hover_item = selenium.find_element(
                By.CSS_SELECTOR, ".workflow-details .column"
            )
            click_item = selenium.find_element(
                By.CSS_SELECTOR, ".column .insert-sibling-button img"
            )
            action_hover_click(selenium, hover_item, click_item).perform()
            time.sleep(0.5)
            hover_item = selenium.find_element(
                By.CSS_SELECTOR, ".workflow-details .week"
            )
            click_item = selenium.find_element(
                By.CSS_SELECTOR, ".week .insert-sibling-button img"
            )
            selenium.find_element(
                By.CSS_SELECTOR, "#sidebar .window-close-button"
            ).click()
            time.sleep(0.5)
            action_hover_click(selenium, hover_item, click_item).perform()
            hover_item = selenium.find_element(
                By.CSS_SELECTOR, ".workflow-details .node"
            )
            click_item = selenium.find_element(
                By.CSS_SELECTOR, ".node .insert-sibling-button img"
            )
            action_hover_click(selenium, hover_item, click_item).perform()
            time.sleep(8)
            self.assertEqual(
                len(
                    selenium.find_elements(
                        By.CSS_SELECTOR, ".workflow-details .column"
                    )
                ),
                num_columns + 1,
            )
            self.assertEqual(
                len(
                    selenium.find_elements(
                        By.CSS_SELECTOR, ".workflow-details .week"
                    )
                ),
                num_weeks + 1,
            )
            self.assertEqual(
                len(
                    selenium.find_elements(
                        By.CSS_SELECTOR, ".workflow-details .node"
                    )
                ),
                num_nodes + 1,
            )
            # Deleting
            hover_item = selenium.find_element(
                By.CSS_SELECTOR, ".workflow-details .node"
            )
            click_item = selenium.find_element(
                By.CSS_SELECTOR, ".node .delete-self-button img"
            )
            action_hover_click(selenium, hover_item, click_item).perform()
            alert = wait.until(expected_conditions.alert_is_present())
            selenium.switch_to.alert.accept()
            time.sleep(1)
            hover_item = selenium.find_element(
                By.CSS_SELECTOR, ".workflow-details .column"
            )
            click_item = selenium.find_element(
                By.CSS_SELECTOR, ".column .delete-self-button img"
            )
            action_hover_click(selenium, hover_item, click_item).perform()
            alert = wait.until(expected_conditions.alert_is_present())
            selenium.switch_to.alert.accept()
            time.sleep(1)
            hover_item = selenium.find_element(
                By.CSS_SELECTOR, ".workflow-details .week"
            )
            click_item = selenium.find_element(
                By.CSS_SELECTOR, ".week .delete-self-button img"
            )
            #            selenium.find_element(By.CSS_SELECTOR,
            #                "#sidebar .window-close-button"
            #            ).click()
            time.sleep(0.5)
            action_hover_click(selenium, hover_item, click_item).perform()
            alert = wait.until(expected_conditions.alert_is_present())
            selenium.switch_to.alert.accept()
            time.sleep(1)
            self.assertEqual(
                len(
                    selenium.find_elements(
                        By.CSS_SELECTOR, ".workflow-details .column"
                    )
                ),
                num_columns,
            )
            self.assertEqual(
                len(
                    selenium.find_elements(
                        By.CSS_SELECTOR, ".workflow-details .week"
                    )
                ),
                num_weeks,
            )
            self.assertEqual(
                len(
                    selenium.find_elements(
                        By.CSS_SELECTOR, ".workflow-details .node"
                    )
                ),
                0,
            )

    def test_workflow_duplication(self):
        print("\nIn method", self._testMethodName, ": ")
        selenium = self.selenium
        wait = WebDriverWait(selenium, timeout=10)
        project = Project.objects.create(author=self.user)
        for workflow_type in ["activity", "course", "program"]:
            workflow = get_model_from_str(workflow_type).objects.create(
                author=self.user
            )
            WorkflowProject.objects.create(workflow=workflow, project=project)
            workflow.weeks.first().nodes.create(
                author=self.user, column=workflow.columns.first()
            )

            selenium.get(
                self.live_server_url
                + reverse("course_flow:workflow-update", args=[workflow.pk])
            )
            time.sleep(2)
            num_columns = workflow.columns.all().count()
            num_weeks = workflow.weeks.all().count()
            num_nodes = 1
            self.assertEqual(
                len(
                    selenium.find_elements(
                        By.CSS_SELECTOR, ".workflow-details .column"
                    )
                ),
                num_columns,
            )
            self.assertEqual(
                len(
                    selenium.find_elements(
                        By.CSS_SELECTOR, ".workflow-details .week"
                    )
                ),
                num_weeks,
            )
            self.assertEqual(
                len(
                    selenium.find_elements(
                        By.CSS_SELECTOR, ".workflow-details .node"
                    )
                ),
                num_nodes,
            )
            hover_item = selenium.find_element(
                By.CSS_SELECTOR, ".workflow-details .column"
            )
            click_item = selenium.find_element(
                By.CSS_SELECTOR, ".column .duplicate-self-button img"
            )
            action_hover_click(selenium, hover_item, click_item).perform()
            time.sleep(1)
            hover_item = selenium.find_element(
                By.CSS_SELECTOR, ".workflow-details .week"
            )
            click_item = selenium.find_element(
                By.CSS_SELECTOR,
                ".week > .mouseover-container-bypass > .mouseover-actions > .duplicate-self-button img",
            )
            selenium.find_element(
                By.CSS_SELECTOR, "#sidebar .window-close-button"
            ).click()
            time.sleep(0.5)
            action_hover_click(selenium, hover_item, click_item).perform()
            time.sleep(1)
            hover_item = selenium.find_element(
                By.CSS_SELECTOR, ".workflow-details .node"
            )
            click_item = selenium.find_element(
                By.CSS_SELECTOR, ".node .duplicate-self-button img"
            )
            action_hover_click(selenium, hover_item, click_item).perform()
            time.sleep(1)
            self.assertEqual(
                len(
                    selenium.find_elements(
                        By.CSS_SELECTOR, ".workflow-details .column"
                    )
                ),
                num_columns + 1,
            )
            self.assertEqual(
                len(
                    selenium.find_elements(
                        By.CSS_SELECTOR, ".workflow-details .week"
                    )
                ),
                num_weeks + 1,
            )
            self.assertEqual(
                len(
                    selenium.find_elements(
                        By.CSS_SELECTOR, ".workflow-details .node"
                    )
                ),
                num_nodes * 2 + 1,
            )

    def test_outcome_editing(self):
        print("\nIn method", self._testMethodName, ": ")
        selenium = self.selenium
        wait = WebDriverWait(selenium, timeout=10)
        project = Project.objects.create(author=self.user)
        workflow = Course.objects.create(author=self.user)
        WorkflowProject.objects.create(workflow=workflow, project=project)
        base_outcome = Outcome.objects.create(author=self.user)
        OutcomeWorkflow.objects.create(outcome=base_outcome, workflow=workflow)
        selenium.get(
            self.live_server_url
            + reverse("course_flow:workflow-update", args=[workflow.pk])
        )
        time.sleep(4)
        selenium.find_element(
            By.CSS_SELECTOR, "a[href='#outcome-bar']"
        ).click()
        selenium.find_element(By.CSS_SELECTOR, "#edit-outcomes-button").click()
        time.sleep(1)
        hover_item = selenium.find_element(
            By.CSS_SELECTOR, ".workflow-details .outcome"
        )
        click_item = selenium.find_element(
            By.CSS_SELECTOR, ".outcome .insert-child-button img"
        )
        action_hover_click(selenium, hover_item, click_item).perform()
        time.sleep(2)

        self.assertEqual(
            len(
                selenium.find_elements(
                    By.CSS_SELECTOR, ".workflow-details .outcome .outcome"
                )
            ),
            1,
        )
        self.assertEqual(
            OutcomeOutcome.objects.filter(parent=base_outcome).count(), 1
        )
        hover_item = selenium.find_element(
            By.CSS_SELECTOR, ".workflow-details .outcome .outcome"
        )
        click_item = selenium.find_element(
            By.CSS_SELECTOR, ".outcome .outcome .insert-sibling-button img"
        )
        action_hover_click(selenium, hover_item, click_item).perform()
        time.sleep(2)
        self.assertEqual(
            len(
                selenium.find_elements(
                    By.CSS_SELECTOR, ".workflow-details .outcome .outcome"
                )
            ),
            2,
        )
        self.assertEqual(
            OutcomeOutcome.objects.filter(parent=base_outcome).count(), 2
        )
        hover_item = selenium.find_element(
            By.CSS_SELECTOR, ".workflow-details .outcome .outcome"
        )
        click_item = selenium.find_element(
            By.CSS_SELECTOR, ".outcome .outcome .delete-self-button img"
        )
        action_hover_click(selenium, hover_item, click_item).perform()
        alert = wait.until(expected_conditions.alert_is_present())
        selenium.switch_to.alert.accept()
        time.sleep(2)
        self.assertEqual(
            len(
                selenium.find_elements(
                    By.CSS_SELECTOR, ".workflow-details .outcome .outcome"
                )
            ),
            1,
        )
        # Make sure item has only been soft deleted
        self.assertEqual(
            OutcomeOutcome.objects.filter(parent=base_outcome).count(), 2
        )
        time.sleep(1)

        hover_item = selenium.find_element(
            By.CSS_SELECTOR, ".workflow-details .outcome .outcome"
        )
        click_item = selenium.find_element(
            By.CSS_SELECTOR, ".outcome .outcome .insert-child-button img"
        )
        action_hover_click(selenium, hover_item, click_item).perform()
        time.sleep(2)
        selenium.find_element(
            By.CSS_SELECTOR, ".outcome:not(.dropped) > .outcome-drop"
        ).click()
        time.sleep(1)
        self.assertEqual(
            len(
                selenium.find_elements(
                    By.CSS_SELECTOR, ".workflow-details .outcome .outcome"
                )
            ),
            2,
        )
        hover_item = selenium.find_element(
            By.CSS_SELECTOR, ".workflow-details .outcome .outcome"
        )
        click_item = selenium.find_element(
            By.CSS_SELECTOR, ".outcome .outcome .duplicate-self-button img"
        )
        action_hover_click(selenium, hover_item, click_item).perform()
        time.sleep(2)
        self.assertEqual(
            len(
                selenium.find_elements(
                    By.CSS_SELECTOR, ".workflow-details .outcome .outcome"
                )
            ),
            3,
        )
        self.assertEqual(
            OutcomeOutcome.objects.filter(parent=base_outcome).count(), 2
        )
        selenium.find_element(By.CSS_SELECTOR, "#add-new-outcome").click()
        time.sleep(3)
        self.assertEqual(Outcome.objects.filter(depth=0).count(), 2)
        self.assertEqual(
            OutcomeWorkflow.objects.filter(workflow=workflow).count(), 2
        )
        hover_item = selenium.find_element(
            By.CSS_SELECTOR, ".workflow-details .outcome-workflow > .outcome"
        )
        click_item = selenium.find_element(
            By.CSS_SELECTOR,
            ".workflow-details .outcome-workflow > .outcome > .mouseover-actions .insert-sibling-button img",
        )
        action_hover_click(selenium, hover_item, click_item).perform()
        time.sleep(2)

        self.assertEqual(
            len(
                selenium.find_elements(
                    By.CSS_SELECTOR,
                    ".workflow-details .outcome-workflow > .outcome",
                )
            ),
            3,
        )
        self.assertEqual(
            OutcomeWorkflow.objects.filter(workflow=workflow).count(), 3
        )
        hover_item = selenium.find_element(
            By.CSS_SELECTOR, ".workflow-details .outcome-workflow > .outcome"
        )
        click_item = selenium.find_element(
            By.CSS_SELECTOR,
            ".workflow-details .outcome-workflow > .outcome > .mouseover-actions .duplicate-self-button img",
        )
        action_hover_click(selenium, hover_item, click_item).perform()
        time.sleep(2)

        self.assertEqual(
            len(
                selenium.find_elements(
                    By.CSS_SELECTOR,
                    ".workflow-details .outcome-workflow > .outcome",
                )
            ),
            4,
        )
        self.assertEqual(
            OutcomeWorkflow.objects.filter(workflow=workflow).count(), 4
        )

    def test_edit_menu(self):
        print("\nIn method", self._testMethodName, ": ")
        # Note that we don't test ALL parts of the edit menu, and we test only for nodes. This will catch the vast majority of potential issues. Linked workflows are tested in a different test
        selenium = self.selenium
        wait = WebDriverWait(selenium, timeout=10)
        project = Project.objects.create(author=self.user)
        for i, workflow_type in enumerate(["activity", "course", "program"]):
            workflow = get_model_from_str(workflow_type).objects.create(
                author=self.user
            )
            WorkflowProject.objects.create(workflow=workflow, project=project)
            workflow.weeks.first().nodes.create(
                author=self.user,
                column=workflow.columns.first(),
                title="test node",
                node_type=i,
            )

            selenium.get(
                self.live_server_url
                + reverse("course_flow:workflow-update", args=[workflow.pk])
            )
            time.sleep(2)
            selenium.find_element(
                By.CSS_SELECTOR, ".workflow-details .node"
            ).click()
            time.sleep(1)
            title = selenium.find_element(By.ID, "title-editor")
            assert "test node" in title.get_attribute("value")
            title.clear()
            title.send_keys("new title")
            time.sleep(2.5)
            assert (
                "new title"
                in selenium.find_element(
                    By.CSS_SELECTOR, ".workflow-details .node .node-title"
                ).text
            )
            self.assertEqual(
                workflow.weeks.first().nodes.first().title, "new title"
            )
            if i < 2:
                context = selenium.find_element(By.ID, "context-editor")
                context.click()
                selenium.find_elements(
                    By.CSS_SELECTOR, "#context-editor option"
                )[2].click()
                time.sleep(2.5)
                self.assertEqual(
                    workflow.weeks.first()
                    .nodes.first()
                    .context_classification,
                    2 + 100 * i,
                )
            else:
                self.assertEqual(
                    len(
                        selenium.find_elements(
                            By.CSS_SELECTOR, "#context-editor"
                        )
                    ),
                    0,
                )
            if i < 2:
                context = selenium.find_element(By.ID, "task-editor")
                context.click()
                selenium.find_elements(By.CSS_SELECTOR, "#task-editor option")[
                    2
                ].click()
                time.sleep(2.5)
                self.assertEqual(
                    workflow.weeks.first().nodes.first().task_classification,
                    2 + 100 * i,
                )
            else:
                self.assertEqual(
                    len(
                        selenium.find_elements(By.CSS_SELECTOR, "#task-editor")
                    ),
                    0,
                )

    def test_project_return(self):
        print("\nIn method", self._testMethodName, ": ")
        selenium = self.selenium
        wait = WebDriverWait(selenium, timeout=10)
        project = Project.objects.create(
            author=self.user, title="project title"
        )
        for i, workflow_type in enumerate(["activity", "course", "program"]):
            workflow = get_model_from_str(workflow_type).objects.create(
                author=self.user
            )
            WorkflowProject.objects.create(workflow=workflow, project=project)
            workflow.weeks.first().nodes.create(
                author=self.user,
                column=workflow.columns.first(),
                title="test node",
                node_type=i,
            )

            selenium.get(
                self.live_server_url
                + reverse("course_flow:workflow-update", args=[workflow.pk])
            )
            time.sleep(2)
            selenium.find_element(By.ID, "project-return").click()
            assert (
                "project title"
                in selenium.find_element(
                    By.CSS_SELECTOR, ".project-title"
                ).text
            )

    def test_strategy_convert(self):
        print("\nIn method", self._testMethodName, ": ")
        selenium = self.selenium
        wait = WebDriverWait(selenium, timeout=10)
        project = Project.objects.create(
            author=self.user, title="project title"
        )
        for i, workflow_type in enumerate(["activity", "course"]):
            workflow = get_model_from_str(workflow_type).objects.create(
                author=self.user
            )
            WorkflowProject.objects.create(workflow=workflow, project=project)
            workflow.weeks.first().nodes.create(
                author=self.user,
                column=workflow.columns.first(),
                title="test node",
                node_type=i,
            )

            selenium.get(
                self.live_server_url
                + reverse("course_flow:workflow-update", args=[workflow.pk])
            )
            time.sleep(5)
            selenium.find_element(
                By.CSS_SELECTOR, ".workflow-details .week"
            ).click()
            time.sleep(1)
            title = selenium.find_element(By.ID, "title-editor").send_keys(
                "new strategy"
            )
            time.sleep(2.5)
            selenium.find_element(By.ID, "toggle-strategy-editor").click()
            time.sleep(4)
            selenium.find_element(
                By.CSS_SELECTOR, "a[href='#node-bar']"
            ).click()
            assert (
                "new strategy"
                in selenium.find_element(
                    By.CSS_SELECTOR, ".strategy-bar-strategy div"
                ).text
            )
            selenium.get(
                self.live_server_url + reverse("course_flow:my-library")
            )
            time.sleep(1)
            selenium.find_element(
                By.CSS_SELECTOR, "." + workflow_type + " .workflow-title"
            ).click()
            time.sleep(2)

            # windows = selenium.window_handles
            # selenium.switch_to_window(windows[0])
            # selenium.close()
            # selenium.switch_to_window(windows[1])

            assert (
                "new strategy"
                in selenium.find_element(
                    By.CSS_SELECTOR, ".project-title"
                ).text
            )
            self.assertEqual(
                Workflow.objects.filter(is_strategy=True).count(), 1
            )
            self.assertEqual(
                Workflow.objects.get(is_strategy=True)
                .weeks.get(is_strategy=True)
                .parent_week,
                workflow.weeks.first(),
            )
            Workflow.objects.get(is_strategy=True).delete()

    # @todo currently crashing app
    def test_outcome_view(self):
        print("\nIn method", self._testMethodName, ": ")
        selenium = self.selenium
        wait = WebDriverWait(selenium, timeout=10)
        project = Project.objects.create(
            author=self.user, title="project title"
        )
        for i, workflow_type in enumerate(["activity", "course", "program"]):
            workflow = get_model_from_str(workflow_type).objects.create(
                author=self.user
            )
            WorkflowProject.objects.create(workflow=workflow, project=project)
            base_outcome = Outcome.objects.create(author=self.user)
            OutcomeWorkflow.objects.create(
                outcome=base_outcome, workflow=workflow
            )
            OutcomeOutcome.objects.create(
                parent=base_outcome,
                child=Outcome.objects.create(author=self.user),
            )
            OutcomeOutcome.objects.create(
                parent=base_outcome,
                child=Outcome.objects.create(author=self.user),
            )
            workflow.weeks.first().nodes.create(
                author=self.user,
                column=workflow.columns.first(),
                title="test node",
                node_type=i,
            )
            workflow.weeks.first().nodes.create(
                author=self.user,
                column=workflow.columns.first(),
                title="test node",
                node_type=i,
            )

            selenium.get(
                self.live_server_url
                + reverse("course_flow:workflow-update", args=[workflow.pk])
            )
            time.sleep(2)
            selenium.find_element(By.CSS_SELECTOR, ".other-views").click()
            selenium.find_element(
                By.CSS_SELECTOR, "#button_outcometable"
            ).click()
            time.sleep(1)
            base_outcome_row_select = ".outcome-table > div > .outcome-row:first-child > .outcome-cells"
            outcome1_row_select = ".outcome-table > div > .outcome-row:first-child+.outcome-row .outcome-cells"
            outcome2_row_select = ".outcome-table > div > .outcome-row:first-child+.outcome-row+.outcome-row .outcome-cells"
            base_cell = (
                base_outcome_row_select
                + " .table-group:first-of-type .total-cell+.table-cell"
            )
            base_cell2 = (
                base_outcome_row_select
                + " .table-group:first-of-type .total-cell+.table-cell+.table-cell"
            )
            base_input = (
                base_outcome_row_select
                + " .table-group:first-of-type .total-cell+.table-cell input"
            )
            base_input2 = (
                base_outcome_row_select
                + " .table-group:first-of-type .total-cell+.table-cell+.table-cell input"
            )
            base_img = (
                base_outcome_row_select
                + " .table-group:first-of-type .total-cell+.table-cell img"
            )
            base_img2 = (
                base_outcome_row_select
                + " .table-group:first-of-type .total-cell+.table-cell+.table-cell img"
            )
            base_total_img = (
                base_outcome_row_select
                + " .table-cell.total-cell:not(.grand-total-cell) img"
            )
            base_grandtotal_img = (
                base_outcome_row_select + " .table-cell.grand-total-cell img"
            )
            base_toggle = action_hover_click(
                selenium,
                selenium.find_element(By.CSS_SELECTOR, base_cell),
                selenium.find_element(By.CSS_SELECTOR, base_input),
            )
            outcome1_cell = (
                outcome1_row_select
                + " .table-group:first-of-type .total-cell+.table-cell"
            )
            outcome1_cell2 = (
                outcome1_row_select
                + " .table-group:first-of-type .total-cell+.table-cell+.table-cell"
            )
            outcome1_input = (
                outcome1_row_select
                + " .table-group:first-of-type .total-cell+.table-cell input"
            )
            outcome1_input2 = (
                outcome1_row_select
                + " .table-group:first-of-type .total-cell+.table-cell+.table-cell input"
            )
            outcome1_img = (
                outcome1_row_select
                + " .table-group:first-of-type .total-cell+.table-cell img"
            )
            outcome1_img2 = (
                outcome1_row_select
                + " .table-group:first-of-type .total-cell+.table-cell+.table-cell img"
            )
            outcome1_total_img = (
                outcome1_row_select
                + " .table-cell.total-cell:not(.grand-total-cell) img"
            )
            outcome1_grandtotal_img = (
                outcome1_row_select + " .table-cell.grand-total-cell img"
            )
            outcome1_toggle = action_hover_click(
                selenium,
                selenium.find_element(By.CSS_SELECTOR, outcome1_cell),
                selenium.find_element(By.CSS_SELECTOR, outcome1_input),
            )
            outcome2_cell = (
                outcome2_row_select
                + " .table-group:first-of-type .total-cell+.table-cell"
            )
            outcome2_cell2 = (
                outcome2_row_select
                + " .table-group:first-of-type .total-cell+.table-cell+.table-cell"
            )
            outcome2_input = (
                outcome2_row_select
                + " .table-group:first-of-type .total-cell+.table-cell input"
            )
            outcome2_input2 = (
                outcome2_row_select
                + " .table-group:first-of-type .total-cell+.table-cell+.table-cell input"
            )
            outcome2_img = (
                outcome2_row_select
                + " .table-group:first-of-type .total-cell+.table-cell img"
            )
            outcome2_img2 = (
                outcome2_row_select
                + " .table-group:first-of-type .total-cell+.table-cell+.table-cell img"
            )
            outcome2_total_img = (
                outcome2_row_select
                + " .table-cell.total-cell:not(.grand-total-cell) img"
            )
            outcome2_grandtotal_img = (
                outcome2_row_select + " .table-cell.grand-total-cell img"
            )
            outcome2_toggle = action_hover_click(
                selenium,
                selenium.find_element(By.CSS_SELECTOR, outcome2_cell),
                selenium.find_element(By.CSS_SELECTOR, outcome2_input),
            )

            def assert_image(element_string, string):
                assert string in selenium.find_element(
                    By.CSS_SELECTOR, element_string
                ).get_attribute("src")

            def assert_no_image(element_string):
                self.assertEqual(
                    len(
                        selenium.find_elements(By.CSS_SELECTOR, element_string)
                    ),
                    0,
                )

            # Toggle the base outcome. Check to make sure the children and totals columns behave as expected
            base_toggle.perform()
            time.sleep(2)
            assert_image(base_img, "solid_check")
            assert_image(base_total_img, "/check")
            assert_image(base_grandtotal_img, "/check")
            assert_image(outcome1_img, "/solid_check")
            assert_image(outcome1_total_img, "/check")
            assert_image(outcome1_grandtotal_img, "/check")
            assert_image(outcome2_img, "/solid_check")
            assert_image(outcome2_total_img, "/check")
            assert_image(outcome2_grandtotal_img, "/check")

            # Toggle one of the children. We expect to lose the top outcome to partial completion
            outcome1_toggle.perform()
            time.sleep(3)
            assert_image(base_img, "/nocheck")
            assert_image(base_total_img, "/nocheck")
            assert_image(base_grandtotal_img, "/nocheck")
            assert_no_image(outcome1_img)
            assert_no_image(outcome1_total_img)
            assert_no_image(outcome1_grandtotal_img)
            assert_image(outcome2_img, "/solid_check")
            assert_image(outcome2_total_img, "/check")
            assert_image(outcome2_grandtotal_img, "/check")
            # check that re-toggling outcome 1 adds the parent
            outcome1_toggle.perform()
            time.sleep(3)
            assert_image(base_img, "solid_check")
            assert_image(base_total_img, "/check")
            assert_image(base_grandtotal_img, "/check")
            assert_image(outcome1_img, "/solid_check")
            assert_image(outcome1_total_img, "/check")
            assert_image(outcome1_grandtotal_img, "/check")
            assert_image(outcome2_img, "/solid_check")
            assert_image(outcome2_total_img, "/check")
            assert_image(outcome2_grandtotal_img, "/check")
            # check that removing the base outcome clears all
            base_toggle.perform()
            time.sleep(3)
            assert_no_image(base_img)
            assert_no_image(base_total_img)
            assert_no_image(base_grandtotal_img)
            assert_no_image(outcome1_img)
            assert_no_image(outcome1_total_img)
            assert_no_image(outcome1_grandtotal_img)
            assert_no_image(outcome2_img)
            assert_no_image(outcome2_total_img)
            assert_no_image(outcome2_grandtotal_img)
            # check completion when not all children are toggled
            outcome1_toggle.perform()
            time.sleep(3)
            assert_image(base_img, "/nocheck")
            assert_image(base_total_img, "/nocheck")
            assert_image(base_grandtotal_img, "/nocheck")
            assert_image(outcome1_img, "solid_check")
            assert_image(outcome1_total_img, "/check")
            assert_image(outcome1_grandtotal_img, "/check")
            assert_no_image(outcome2_img)
            assert_no_image(outcome2_total_img)
            assert_no_image(outcome2_grandtotal_img)
            # check completion when children are toggled but in different nodes
            action_hover_click(
                selenium,
                selenium.find_element(By.CSS_SELECTOR, outcome2_cell2),
                selenium.find_element(By.CSS_SELECTOR, outcome2_input2),
            ).perform()
            time.sleep(3)

            assert_image(base_img, "/nocheck")
            assert_image(base_img2, "/nocheck")
            assert_image(base_total_img, "/check")
            assert_image(base_grandtotal_img, "/check")
            assert_image(outcome1_img, "solid_check")
            assert_no_image(outcome1_img2)
            assert_image(outcome1_total_img, "/check")
            assert_image(outcome1_grandtotal_img, "/check")
            assert_no_image(outcome2_img)
            assert_image(outcome2_img2, "solid_check")
            assert_image(outcome2_total_img, "/check")
            assert_image(outcome2_grandtotal_img, "/check")

    def test_outcome_analytics(self):
        print("\nIn method", self._testMethodName, ": ")
        selenium = self.selenium
        wait = WebDriverWait(selenium, timeout=10)

        # Create test data
        project = Project.objects.create(
            author=self.user, title="project title"
        )
        course = Course.objects.create(author=self.user)
        program = Program.objects.create(author=self.user)
        # add course and program to project
        WorkflowProject.objects.create(workflow=course, project=project)
        WorkflowProject.objects.create(workflow=program, project=project)

        base_outcome = Outcome.objects.create(author=self.user)
        OutcomeWorkflow.objects.create(outcome=base_outcome, workflow=program)
        poo1 = OutcomeOutcome.objects.create(
            parent=base_outcome,
            child=Outcome.objects.create(author=self.user),
        )
        poo2 = OutcomeOutcome.objects.create(
            parent=base_outcome,
            child=Outcome.objects.create(author=self.user),
        )
        coc1 = course.outcomes.create(author=self.user)
        coc2 = course.outcomes.create(author=self.user)
        node = program.weeks.first().nodes.create(
            author=self.user,
            linked_workflow=course,
            column=program.columns.first(),
        )
        response = self.client.post(
            reverse("course_flow:json-api-post-update-outcomenode-degree"),
            {"nodePk": node.id, "outcomePk": base_outcome.id, "degree": 1},
        )

        OutcomeHorizontalLink.objects.create(
            outcome=coc1, parent_outcome=poo1.child
        )
        OutcomeHorizontalLink.objects.create(
            outcome=coc2, parent_outcome=poo2.child
        )

        # Navigate to URL
        selenium.get(
            self.live_server_url
            + reverse("course_flow:workflow-update", args=[program.pk])
        )
        other_views = wait.until(
            EC.element_to_be_clickable((By.CSS_SELECTOR, ".other-views"))
        )
        other_views.click()

        # click the Course Outcome Analytics button
        selenium.find_element(
            By.CSS_SELECTOR, "#button_alignmentanalysis"
        ).click()

        wait.until(
            EC.presence_of_all_elements_located(
                (By.CSS_SELECTOR, ".week .title-text")
            )
        )

        title_text = selenium.find_elements(
            By.CSS_SELECTOR, ".week .title-text"
        )[0]

        assert title_text.text == "Term 1"

        assert len(selenium.find_elements(By.CSS_SELECTOR, ".week .node")) == 1

        assert (
            len(
                selenium.find_elements(
                    By.CSS_SELECTOR, ".week .node .child-outcome"
                )
            )
            == 3
        )

        assert (
            len(
                selenium.find_elements(
                    By.CSS_SELECTOR,
                    ".week .node .child-outcome .half-width>.outcome",
                )
            )
            == 2
        )
        assert (
            len(
                selenium.find_elements(
                    By.CSS_SELECTOR,
                    ".week .node .child-outcome .alignment-row .outcome",
                )
            )
            == 2
        )

    def test_outcome_matrix_view(self):
        print("\nIn method", self._testMethodName, ": ")
        selenium = self.selenium
        wait = WebDriverWait(selenium, timeout=10)
        project = Project.objects.create(
            author=self.user, title="project title"
        )
        course = Course.objects.create(author=self.user)
        program = Program.objects.create(author=self.user)
        WorkflowProject.objects.create(workflow=course, project=project)
        WorkflowProject.objects.create(workflow=program, project=project)
        base_outcome = Outcome.objects.create(author=self.user)
        OutcomeWorkflow.objects.create(outcome=base_outcome, workflow=program)
        # program outcome-outcome
        poo1 = OutcomeOutcome.objects.create(
            parent=base_outcome,
            child=Outcome.objects.create(author=self.user),
        )
        poo2 = OutcomeOutcome.objects.create(
            parent=base_outcome,
            child=Outcome.objects.create(author=self.user),
        )
        coc1 = course.outcomes.create(author=self.user)
        coc2 = course.outcomes.create(author=self.user)
        node = program.weeks.first().nodes.create(
            author=self.user,
            linked_workflow=course,
            column=program.columns.first(),
        )
        response = self.client.post(
            reverse("course_flow:json-api-post-update-outcomenode-degree"),
            {"nodePk": node.id, "outcomePk": poo1.child.pk, "degree": 1},
        )

        selenium.get(
            self.live_server_url
            + reverse("course_flow:workflow-update", args=[program.pk])
        )
        time.sleep(2)
        selenium.find_element(By.CSS_SELECTOR, ".other-views").click()
        selenium.find_element(By.CSS_SELECTOR, "#button_outcometable").click()
        time.sleep(2)

        selenium.find_element(By.CSS_SELECTOR, "#table_type_matrix").click()
        time.sleep(2)
        assert (
            len(selenium.find_elements(By.CSS_SELECTOR, ".table-cell .node"))
            == 1
        )
        assert (
            len(selenium.find_elements(By.CSS_SELECTOR, ".table-cell input"))
            == 1
        )
        time.sleep(2)
        assert (
            len(selenium.find_elements(By.CSS_SELECTOR, ".table-cell > img"))
            == 3
        )

    def test_grid_view(self):
        print("\nIn method", self._testMethodName, ": ")
        selenium = self.selenium
        wait = WebDriverWait(selenium, timeout=10)
        project = Project.objects.create(
            author=self.user, title="project title"
        )
        program = Program.objects.create(author=self.user)
        WorkflowProject.objects.create(workflow=program, project=project)
        node = program.weeks.first().nodes.create(
            author=self.user,
            column=program.columns.first(),
        )

        selenium.get(
            self.live_server_url
            + reverse("course_flow:workflow-update", args=[program.pk])
        )
        time.sleep(2)
        selenium.find_element(By.CSS_SELECTOR, ".other-views").click()
        selenium.find_element(By.CSS_SELECTOR, "#button_grid").click()
        time.sleep(1)
        assert (
            len(selenium.find_elements(By.CSS_SELECTOR, ".workflow-grid")) > 0
        )

    def test_linked_workflow(self):
        print("\nIn method", self._testMethodName, ": ")
        selenium = self.selenium
        selenium.set_window_size(
            1920, 1480
        )  # need to expand the window size to show the right sidebar buttons
        workflow_types = ["activity", "course", "program"]

        wait = WebDriverWait(selenium, timeout=10)
        project = Project.objects.create(
            author=self.user, title="project title"
        )

        for i, workflow_type in enumerate(workflow_types):
            print("creating: " + workflow_type)
            workflow = get_model_from_str(workflow_type).objects.create(
                author=self.user, title=workflow_type
            )
            WorkflowProject.objects.create(workflow=workflow, project=project)
            workflow.weeks.first().nodes.create(
                author=self.user,
                column=workflow.columns.first(),
                title="test node",
                node_type=i,
            )

            # app is crashing without this
            time.sleep(1)
            # navigate to newly created workflow page
            selenium.get(
                self.live_server_url
                + reverse("course_flow:workflow-update", args=[workflow.pk])
            )

            this_url = selenium.current_url

            # why?
            if workflow_type == "activity":
                continue

            node_details = wait.until(
                EC.element_to_be_clickable(
                    (By.CSS_SELECTOR, ".workflow-details .node .node-title")
                )
            )
            node_details.click()

            linked_workflow_editor = wait.until(
                EC.element_to_be_clickable((By.ID, "linked-workflow-editor"))
            )
            linked_workflow_editor.click()

            section_workflow_menu = wait.until(
                EC.element_to_be_clickable(
                    (
                        By.CSS_SELECTOR,
                        ".section-"
                        + workflow_types[i - 1]
                        + " .workflow-for-menu",
                    )
                )
            )
            section_workflow_menu.click()

            # link to node button
            set_linked_workflow = wait.until(
                EC.element_to_be_clickable((By.ID, "set-linked-workflow"))
            )
            set_linked_workflow.click()

            set_linked_workflow = wait.until(
                EC.element_to_be_clickable(
                    (By.CSS_SELECTOR, ".linked-workflow.hover-shade")
                )
            )

            self.assertEqual(
                workflow.weeks.first().nodes.first().linked_workflow.id,
                get_model_from_str(workflow_types[i - 1]).objects.first().id,
            )
            linked_workflow_cta = wait.until(
                EC.element_to_be_clickable(
                    (
                        By.CSS_SELECTOR,
                        ".workflow-details .node .linked-workflow",
                    )
                )
            )
            linked_workflow_cta.click()

            windows = selenium.window_handles
            if len(windows) > 0:
                selenium.switch_to.window(windows[0])
                selenium.close()
            else:
                # Handle the case where no windows are left
                print("No more windows to switch to.")

            windows = selenium.window_handles
            if len(windows) > 0:
                selenium.switch_to.window(windows[0])
            else:
                # Handle the case where no windows are left
                print("No more windows to switch to.")

            wait.until(
                EC.presence_of_element_located(
                    (By.CSS_SELECTOR, ".project-title")
                )
            )
            assert (
                workflow_types[i - 1]
                in selenium.find_element(
                    By.CSS_SELECTOR, ".project-title"
                ).text
            )
            selenium.get(this_url)
            time.sleep(2)
            ActionChains(selenium).move_to_element_with_offset(
                selenium.find_element(
                    By.CSS_SELECTOR, ".workflow-details .node .node-title"
                ),
                5,
                5,
            ).click().perform()

            linked_workflow_editor = wait.until(
                EC.element_to_be_clickable((By.ID, "linked-workflow-editor"))
            )
            linked_workflow_editor.click()

            section_workflow_menu = wait.until(
                EC.element_to_be_clickable(
                    (
                        By.CSS_SELECTOR,
                        ".section-"
                        + workflow_types[i - 1]
                        + " .workflow-for-menu",
                    )
                )
            )
            section_workflow_menu.click()

            selenium.find_element(By.ID, "set-linked-workflow-none").click()
            time.sleep(2)

            self.assertEqual(
                workflow.weeks.first().nodes.first().linked_workflow, None
            )
            ActionChains(selenium).move_to_element_with_offset(
                selenium.find_element(
                    By.CSS_SELECTOR, ".workflow-details .node .node-title"
                ),
                5,
                5,
            ).double_click().perform()
            assert (
                workflow_type
                in selenium.find_element(
                    By.CSS_SELECTOR, ".project-title"
                ).text
            )

    def create_many_items(self, author, published, disciplines):
        print("\nIn method", self._testMethodName, ": ")
        for object_type in [
            "activity",
            "course",
            "program",
            "project",
        ]:
            for i in range(10):
                item = get_model_from_str(object_type).objects.create(
                    author=author,
                    published=published,
                    title=object_type + str(i),
                )
                if object_type in ["activity", "course", "program"]:
                    item.weeks.first().nodes.create(author=author)
                    item.weeks.first().nodes.create(author=author)
                    item.weeks.first().nodes.create(author=author)
                else:
                    item.disciplines.set(disciplines)
                    WorkflowProject.objects.create(
                        workflow=Activity.objects.filter(project=None).first(),
                        project=item,
                    )
                    WorkflowProject.objects.create(
                        workflow=Course.objects.filter(project=None).first(),
                        project=item,
                    )
                    WorkflowProject.objects.create(
                        workflow=Program.objects.filter(project=None).first(),
                        project=item,
                    )

    # why does this method get invoked 3 times by test runner?
    # this test working again but needs a lot of work to remove the time.sleeps
    def test_explore(self):
        print("\nIn method", self._testMethodName, ": ")

        # helper functions
        def count_pagination_and_elements():
            # find all the paginated page links and count them  (4)
            created_by_buttons = wait.until(
                EC.presence_of_all_elements_located(page_buttons_selector)
            )
            self.assertEqual(len(created_by_buttons), 4)
            # count the number of articles (20)
            workflow_title = wait.until(
                EC.presence_of_all_elements_located(
                    (By.CSS_SELECTOR, ".workflow-title")
                )
            )
            self.assertEqual(len(workflow_title), 20)

        def has_loading_finished():
            # not sure why this check is not working, use time sleep as a 'hack/stopgap' until we can fix it
            # it might not be worth fixing until we have full SPA, then we can attach a loading class to the body, for example
            # if we design a good general 'ajax done' test, should move it into global scope
            wait.until(EC.element_to_be_clickable((By.ID, "prev-page-button")))
            time.sleep(5)

        print("\nIn method", self._testMethodName, ": ")
        selenium = self.selenium
        selenium.set_window_size(
            1920, 1480
        )  # need to expand the window size to show the right sidebar buttons
        wait = WebDriverWait(selenium, timeout=10)
        author = get_author()
        discipline = Discipline.objects.create(title="Discipline1")

        #
        search_button_selector = (By.CSS_SELECTOR, "#workflow-search+button")
        page_buttons_selector = (By.CSS_SELECTOR, ".page-button")

        # how many items does create_many_items create?
        self.create_many_items(author, True, disciplines=[discipline])
        self.create_many_items(author, True, disciplines=[discipline])

        # navigate to URL
        selenium.get(self.live_server_url + reverse("course_flow:explore"))

        # open the filters menu
        selenium.find_element(By.ID, "workflow-filter").click()

        # select all filter checkboxes
        for checkbox in selenium.find_elements(
            By.CSS_SELECTOR, "#workflow-filter input"
        ):
            checkbox.click()

        # click search button
        search_button = wait.until(
            EC.element_to_be_clickable(search_button_selector)
        )
        search_button.click()

        # count pagination and elements
        count_pagination_and_elements()

        # go to page 2
        selenium.find_elements(By.CSS_SELECTOR, ".page-button")[2].click()
        has_loading_finished()
        count_pagination_and_elements()

        # check that page 2 button CTA is active
        page_buttons = wait.until(
            EC.presence_of_all_elements_located(page_buttons_selector)
        )
        assert "active" in page_buttons[2].get_attribute("class")

        # paginate right
        selenium.find_element(By.CSS_SELECTOR, "#next-page-button").click()

        has_loading_finished()
        count_pagination_and_elements()

        # check that page 3 button CTA is active
        page_buttons = wait.until(
            EC.presence_of_all_elements_located(page_buttons_selector)
        )
        assert "active" in page_buttons[3].get_attribute("class")

        selenium.find_element(By.CSS_SELECTOR, "#prev-page-button").click()

        created_by_buttons = wait.until(
            EC.presence_of_all_elements_located(
                (By.CSS_SELECTOR, ".page-button")
            )
        )
        self.assertEqual(len(created_by_buttons), 4)
        self.assertEqual(
            len(selenium.find_elements(By.CSS_SELECTOR, ".workflow-title")), 20
        )
        has_loading_finished()
        count_pagination_and_elements()

        page_buttons = wait.until(
            EC.presence_of_all_elements_located(page_buttons_selector)
        )
        assert "active" in page_buttons[2].get_attribute("class")

        # open the disciplines menu
        selenium.find_element(By.ID, "workflow-disciplines").click()

        # check all the disciplines checkboxes
        for checkbox in selenium.find_elements(
            By.CSS_SELECTOR, "#workflow-disciplines input"
        ):
            checkbox.click()

        # click search button again
        search_button = wait.until(
            EC.element_to_be_clickable(search_button_selector)
        )
        search_button.click()
        has_loading_finished()

        count_pagination_and_elements()

        # still failing here
        workflow_search_input = selenium.find_element(
            By.ID, "workflow-search-input"
        )
        workflow_search_input.send_keys("1")
        workflow_search_input.send_keys(Keys.TAB)
        wait.until_not(
            lambda driver: selenium.find_element(
                *search_button_selector
            ).get_attribute("disabled")
        )
        search_button = wait.until(
            EC.element_to_be_clickable(search_button_selector)
        )
        search_button.click()

        # has_loading_finished()
        time.sleep(5)

        self.assertEqual(
            len(selenium.find_elements(By.CSS_SELECTOR, ".workflow-title")), 8
        )
        self.assertEqual(
            len(selenium.find_elements(By.CSS_SELECTOR, ".page-button")), 1
        )
        for button in selenium.find_elements(
            By.CSS_SELECTOR, ".workflow-toggle-favourite"
        ):
            button.click()

        time.sleep(5)

        self.assertEqual(
            Favourite.objects.filter(
                user=self.user,
                content_type=ContentType.objects.get_for_model(Project),
            ).count(),
            2,
        )
        self.assertEqual(
            Favourite.objects.filter(
                user=self.user,
                content_type=ContentType.objects.get_for_model(Workflow),
            ).count(),
            6,
        )

    def test_explore_no_publish(self):
        print("\nIn method", self._testMethodName, ": ")
        selenium = self.selenium
        wait = WebDriverWait(selenium, timeout=10)
        author = get_author()
        discipline = Discipline.objects.create(title="Discipline1")
        self.create_many_items(author, False, disciplines=[discipline])
        selenium.get(self.live_server_url + reverse("course_flow:explore"))
        selenium.find_element(
            By.CSS_SELECTOR, "#workflow-search+button"
        ).click()
        self.assertEqual(
            len(selenium.find_elements(By.CSS_SELECTOR, ".page-button")), 0
        )
        self.assertEqual(
            len(selenium.find_elements(By.CSS_SELECTOR, ".workflow-title")), 0
        )

    def test_explore_disciplines(self):
        print("\nIn method", self._testMethodName, ": ")
        selenium = self.selenium
        wait = WebDriverWait(selenium, timeout=10)
        author = get_author()
        discipline1 = Discipline.objects.create(title="Discipline1")
        discipline2 = Discipline.objects.create(title="Discipline2")
        self.create_many_items(author, True, disciplines=[discipline1])
        self.create_many_items(author, True, disciplines=[discipline2])
        self.create_many_items(
            author, True, disciplines=[discipline1, discipline2]
        )
        selenium.get(self.live_server_url + reverse("course_flow:explore"))
        selenium.find_element(
            By.CSS_SELECTOR, "#workflow-search+button"
        ).click()
        time.sleep(1)
        self.assertEqual(
            selenium.find_elements(By.CSS_SELECTOR, ".page-button")[4].text,
            "6",
        )
        self.assertEqual(
            len(selenium.find_elements(By.CSS_SELECTOR, ".workflow-title")), 20
        )
        selenium.find_element(By.ID, "workflow-disciplines").click()
        selenium.find_elements(By.CSS_SELECTOR, "#workflow-disciplines input")[
            0
        ].click()
        selenium.find_element(
            By.CSS_SELECTOR, "#workflow-search+button"
        ).click()
        time.sleep(1)
        self.assertEqual(
            len(selenium.find_elements(By.CSS_SELECTOR, ".page-button")), 4
        )
        self.assertEqual(
            len(selenium.find_elements(By.CSS_SELECTOR, ".workflow-title")), 20
        )
        selenium.find_element(By.ID, "workflow-disciplines").click()
        selenium.find_elements(By.CSS_SELECTOR, "#workflow-disciplines input")[
            0
        ].click()
        selenium.find_elements(By.CSS_SELECTOR, "#workflow-disciplines input")[
            1
        ].click()
        selenium.find_element(
            By.CSS_SELECTOR, "#workflow-search+button"
        ).click()
        time.sleep(1)
        self.assertEqual(
            len(selenium.find_elements(By.CSS_SELECTOR, ".page-button")), 4
        )
        self.assertEqual(
            len(selenium.find_elements(By.CSS_SELECTOR, ".workflow-title")), 20
        )
        selenium.find_element(By.ID, "workflow-disciplines").click()
        selenium.find_elements(By.CSS_SELECTOR, "#workflow-disciplines input")[
            0
        ].click()
        selenium.find_element(
            By.CSS_SELECTOR, "#workflow-search+button"
        ).click()
        time.sleep(1)
        self.assertEqual(
            selenium.find_elements(By.CSS_SELECTOR, ".page-button")[4].text,
            "6",
        )
        self.assertEqual(
            len(selenium.find_elements(By.CSS_SELECTOR, ".workflow-title")), 20
        )

    def test_share_edit_view(self):
        print("\nIn method", self._testMethodName, ": ")
        selenium = self.selenium
        wait = WebDriverWait(selenium, timeout=10)
        user2 = get_author()
        project = Project.objects.create(author=self.user)
        project.title = "Title"
        project.save()
        discipline = Discipline.objects.create(title="discipline")
        project.disciplines.add(discipline)
        selenium.get(
            self.live_server_url
            + reverse("course_flow:project-update", args=[project.pk])
        )
        selenium.find_element(By.ID, "share-button").click()
        inputs = selenium.find_elements(By.CSS_SELECTOR, ".user-add input")
        inputs[0].send_keys("testuser2")
        time.sleep(2)
        selenium.find_elements(By.CSS_SELECTOR, ".ui-autocomplete li")[
            0
        ].click()
        time.sleep(0.5)
        selenium.find_elements(By.CSS_SELECTOR, ".user-add button")[0].click()
        time.sleep(2)
        self.assertEqual(
            ObjectPermission.objects.filter(
                user=user2,
                permission_type=ObjectPermission.PERMISSION_EDIT,
                content_type=ContentType.objects.get_for_model(project),
                object_id=project.id,
            ).count(),
            1,
        )

        selenium.find_elements(By.CSS_SELECTOR, ".user-label select")[
            0
        ].click()
        selenium.find_elements(By.CSS_SELECTOR, ".user-label select option")[
            1
        ].click()
        time.sleep(2)
        self.assertEqual(
            ObjectPermission.objects.filter(
                user=user2,
                permission_type=ObjectPermission.PERMISSION_EDIT,
                content_type=ContentType.objects.get_for_model(project),
                object_id=project.id,
            ).count(),
            0,
        )
        self.assertEqual(
            ObjectPermission.objects.filter(
                user=user2,
                permission_type=ObjectPermission.PERMISSION_COMMENT,
                content_type=ContentType.objects.get_for_model(project),
                object_id=project.id,
            ).count(),
            1,
        )
        selenium.find_elements(By.CSS_SELECTOR, ".user-label select")[
            0
        ].click()
        selenium.find_elements(By.CSS_SELECTOR, ".user-label select option")[
            2
        ].click()
        time.sleep(2)
        self.assertEqual(
            ObjectPermission.objects.filter(
                user=user2,
                permission_type=ObjectPermission.PERMISSION_COMMENT,
                content_type=ContentType.objects.get_for_model(project),
                object_id=project.id,
            ).count(),
            0,
        )
        self.assertEqual(
            ObjectPermission.objects.filter(
                user=user2,
                permission_type=ObjectPermission.PERMISSION_VIEW,
                content_type=ContentType.objects.get_for_model(project),
                object_id=project.id,
            ).count(),
            1,
        )
        selenium.find_elements(By.CSS_SELECTOR, ".user-label select")[
            0
        ].click()
        selenium.find_elements(By.CSS_SELECTOR, ".user-label select option")[
            3
        ].click()
        alert = wait.until(expected_conditions.alert_is_present())
        selenium.switch_to.alert.accept()
        time.sleep(2)
        self.assertEqual(
            ObjectPermission.objects.filter(
                user=user2,
                permission_type=ObjectPermission.PERMISSION_VIEW,
                content_type=ContentType.objects.get_for_model(project),
                object_id=project.id,
            ).count(),
            0,
        )
        selenium.find_element(By.CSS_SELECTOR, ".make-public").click()
        alert = wait.until(expected_conditions.alert_is_present())
        selenium.switch_to.alert.accept()
        time.sleep(2)
        project = Project.objects.get(pk=project.pk)
        self.assertEqual(project.published, True)

        selenium.find_element(
            By.CSS_SELECTOR, ".message-wrap > .window-close-button"
        ).click()
        self.assertEqual(
            len(selenium.find_elements(By.CSS_SELECTOR, ".message-wrap")), 0
        )


class SeleniumDeleteRestoreTestCase(ChannelsStaticLiveServerTestCase):
    def setUp(self):
        selbase = SeleniumBase()
        self.selenium = selbase.init_selenium()

        super().setUp()
        selenium = self.selenium
        selenium.maximize_window()

        self.user = login(self)
        selenium.get(self.live_server_url + "/course-flow/home/")
        username = selenium.find_element(By.ID, "id_username")
        password = selenium.find_element(By.ID, "id_password")
        username.send_keys("testuser1")
        password.send_keys("testpass1")
        selenium.find_element(By.CSS_SELECTOR, "button[type=Submit]").click()

    def tearDown(self):
        self.selenium.quit()
        super().tearDown()

    def create_many_items(self, author, published, disciplines):
        print("\nIn method", self._testMethodName, ": ")
        for object_type in [
            "activity",
            "course",
            "program",
            "project",
        ]:
            for i in range(10):
                item = get_model_from_str(object_type).objects.create(
                    author=author,
                    published=published,
                    title=object_type + str(i),
                )
                if object_type in ["activity", "course", "program"]:
                    item.weeks.first().nodes.create(author=author)
                    item.weeks.first().nodes.create(author=author)
                    item.weeks.first().nodes.create(author=author)
                else:
                    item.disciplines.set(disciplines)
                    WorkflowProject.objects.create(
                        workflow=Activity.objects.filter(project=None).first(),
                        project=item,
                    )
                    WorkflowProject.objects.create(
                        workflow=Course.objects.filter(project=None).first(),
                        project=item,
                    )
                    WorkflowProject.objects.create(
                        workflow=Program.objects.filter(project=None).first(),
                        project=item,
                    )

    # Begin tests
    def test_delete_restore_column(self):
        print("\nIn method", self._testMethodName, ": ")
        selenium = self.selenium
        wait = WebDriverWait(selenium, timeout=10)

        project = Project.objects.create(author=self.user)

        for workflow_type in ["activity", "course", "program"]:
            # create test data
            # create one of activity, course, program and add to the previously created project
            workflow = get_model_from_str(workflow_type).objects.create(
                author=self.user
            )
            WorkflowProject.objects.create(workflow=workflow, project=project)
            workflow.weeks.first().nodes.create(
                author=self.user, column=workflow.columns.first()
            )
            workflow.outcomes.create(author=self.user)
            workflow.outcomes.first().children.create(author=self.user)

            selenium.get(
                self.live_server_url
                + reverse("course_flow:workflow-update", args=[workflow.pk])
            )
            time.sleep(2)

            # Delete a column
            column_id = workflow.columns.first().id

            self.assertEqual(
                len(
                    selenium.find_elements(
                        By.CSS_SELECTOR,
                        ".workflow-details .node.column-" + str(column_id),
                    )
                ),
                1,
            )
            hover_item = selenium.find_element(
                By.CSS_SELECTOR, ".workflow-details .column"
            )
            click_item = selenium.find_element(
                By.CSS_SELECTOR, ".column .delete-self-button img"
            )
            action_hover_click(selenium, hover_item, click_item).perform()
            alert = wait.until(expected_conditions.alert_is_present())
            selenium.switch_to.alert.accept()
            time.sleep(2)
            column2_id = workflow.columns.filter(deleted=False).first().id

            # Make sure all nodes have been moved to the first column
            self.assertEqual(
                len(
                    selenium.find_elements(
                        By.CSS_SELECTOR,
                        ".workflow-details .node.column-" + str(column2_id),
                    )
                ),
                1,
            )

            # Refresh, and make sure the change is permanent
            selenium.get(
                self.live_server_url
                + reverse("course_flow:workflow-update", args=[workflow.pk])
            )
            time.sleep(2)
            self.assertEqual(
                len(
                    selenium.find_elements(
                        By.CSS_SELECTOR,
                        ".workflow-details .node.column-" + str(column2_id),
                    )
                ),
                1,
            )

            # Restore the column
            selenium.find_element(
                By.CSS_SELECTOR, "a[href='#restore-bar'] span"
            ).click()
            selenium.find_element(
                By.CSS_SELECTOR,
                "#restore-bar-workflow .node-bar-column-block button",
            ).click()
            time.sleep(2)

            # Make sure all nodes have been moved back to the restored column
            self.assertEqual(
                len(
                    selenium.find_elements(
                        By.CSS_SELECTOR,
                        ".workflow-details .node.column-" + str(column_id),
                    )
                ),
                1,
            )

            # Refresh, and make sure the change is permanent
            selenium.get(
                self.live_server_url
                + reverse("course_flow:workflow-update", args=[workflow.pk])
            )
            time.sleep(2)
            self.assertEqual(
                len(
                    selenium.find_elements(
                        By.CSS_SELECTOR,
                        ".workflow-details .node.column-" + str(column_id),
                    )
                ),
                1,
            )

    def test_delete_restore_node(self):
        print("\nIn method", self._testMethodName, ": ")
        selenium = self.selenium
        wait = WebDriverWait(selenium, timeout=10)
        project = Project.objects.create(author=self.user)
        for workflow_type in ["activity", "course", "program"]:
            workflow = get_model_from_str(workflow_type).objects.create(
                author=self.user
            )
            WorkflowProject.objects.create(workflow=workflow, project=project)
            workflow.weeks.first().nodes.create(
                author=self.user, column=workflow.columns.first()
            )
            workflow.outcomes.create(author=self.user)
            workflow.outcomes.first().children.create(author=self.user)
            selenium.get(
                self.live_server_url
                + reverse("course_flow:workflow-update", args=[workflow.pk])
            )
            time.sleep(2)

            # Delete a node
            self.assertEqual(
                len(
                    selenium.find_elements(
                        By.CSS_SELECTOR, ".workflow-details .node"
                    )
                ),
                1,
            )
            hover_item = selenium.find_element(
                By.CSS_SELECTOR, ".workflow-details .node"
            )
            click_item = selenium.find_element(
                By.CSS_SELECTOR, ".node .delete-self-button img"
            )
            action_hover_click(selenium, hover_item, click_item).perform()
            alert = wait.until(expected_conditions.alert_is_present())
            selenium.switch_to.alert.accept()
            time.sleep(2)

            # Make sure the node has vanished
            self.assertEqual(
                len(
                    selenium.find_elements(
                        By.CSS_SELECTOR, ".workflow-details .node"
                    )
                ),
                0,
            )

            # Refresh, and make sure the change is permanent
            selenium.get(
                self.live_server_url
                + reverse("course_flow:workflow-update", args=[workflow.pk])
            )
            time.sleep(2)
            self.assertEqual(
                len(
                    selenium.find_elements(
                        By.CSS_SELECTOR, ".workflow-details .node"
                    )
                ),
                0,
            )

            # Restore the node
            selenium.find_element(
                By.CSS_SELECTOR, "a[href='#restore-bar'] span"
            ).click()
            selenium.find_element(
                By.CSS_SELECTOR,
                "#restore-bar-workflow .node-bar-column-block button",
            ).click()
            time.sleep(2)

            # Make sure the node was restored
            self.assertEqual(
                len(
                    selenium.find_elements(
                        By.CSS_SELECTOR, ".workflow-details .node"
                    )
                ),
                1,
            )

            # Refresh, and make sure the change is permanent
            selenium.get(
                self.live_server_url
                + reverse("course_flow:workflow-update", args=[workflow.pk])
            )
            time.sleep(2)
            self.assertEqual(
                len(
                    selenium.find_elements(
                        By.CSS_SELECTOR, ".workflow-details .node"
                    )
                ),
                1,
            )

    def test_delete_restore_outcome(self):
        print("\nIn method", self._testMethodName, ": ")
        selenium = self.selenium
        wait = WebDriverWait(selenium, timeout=10)
        project = Project.objects.create(author=self.user)
        for workflow_type in ["activity", "course", "program"]:
            workflow = get_model_from_str(workflow_type).objects.create(
                author=self.user
            )
            WorkflowProject.objects.create(workflow=workflow, project=project)
            node1 = workflow.weeks.first().nodes.create(
                author=self.user, column=workflow.columns.first()
            )
            node2 = workflow.weeks.first().nodes.create(
                author=self.user, column=workflow.columns.first()
            )
            outcome = workflow.outcomes.create(author=self.user)
            outcome2 = workflow.outcomes.create(author=self.user)

            child1 = workflow.outcomes.first().children.create(
                author=self.user, depth=1
            )
            child2 = workflow.outcomes.first().children.create(
                author=self.user, depth=1
            )
            OutcomeNode.objects.create(node=node1, outcome=child1)
            OutcomeNode.objects.create(node=node1, outcome=outcome2)
            OutcomeNode.objects.create(node=node2, outcome=outcome)
            OutcomeNode.objects.create(node=node2, outcome=outcome2)
            selenium.get(
                self.live_server_url
                + reverse("course_flow:workflow-update", args=[workflow.pk])
            )
            time.sleep(3)
            ActionChains(selenium).move_to_element(
                selenium.find_elements(By.CSS_SELECTOR, ".node .side-actions")[
                    1
                ]
            ).perform()
            self.assertEqual(
                len(
                    selenium.find_elements(
                        By.CSS_SELECTOR,
                        ".outcome-node .outcome-" + str(outcome.id),
                    )
                ),
                1,
            )
            ActionChains(selenium).move_to_element(
                selenium.find_elements(By.CSS_SELECTOR, ".node .side-actions")[
                    0
                ]
            ).perform()
            self.assertEqual(
                len(
                    selenium.find_elements(
                        By.CSS_SELECTOR,
                        ".outcome-node .outcome-" + str(child1.id),
                    )
                ),
                1,
            )

            selenium.find_element(
                By.CSS_SELECTOR, "#button_outcomeedit"
            ).click()
            time.sleep(3)
            # Delete the parent outcome
            hover_item = selenium.find_element(
                By.CSS_SELECTOR, ".outcome-edit .outcome-workflow > .outcome"
            )
            click_item = selenium.find_element(
                By.CSS_SELECTOR,
                ".outcome-edit .outcome-workflow > .outcome>.mouseover-actions .delete-self-button img",
            )
            action_hover_click(selenium, hover_item, click_item).perform()
            alert = wait.until(expected_conditions.alert_is_present())
            selenium.switch_to.alert.accept()
            time.sleep(2)
            selenium.find_element(
                By.CSS_SELECTOR, "#button_workflowview"
            ).click()

            # Make sure the outcomenodes have vanished
            time.sleep(1)
            ActionChains(selenium).move_to_element(
                selenium.find_elements(By.CSS_SELECTOR, ".node .side-actions")[
                    1
                ]
            ).perform()
            self.assertEqual(
                len(
                    selenium.find_elements(
                        By.CSS_SELECTOR,
                        ".outcome-node .outcome-" + str(outcome.id),
                    )
                ),
                0,
            )
            ActionChains(selenium).move_to_element(
                selenium.find_elements(By.CSS_SELECTOR, ".node .side-actions")[
                    0
                ]
            ).perform()
            self.assertEqual(
                len(
                    selenium.find_elements(
                        By.CSS_SELECTOR,
                        ".outcome-node .outcome-" + str(child1.id),
                    )
                ),
                0,
            )

            # Refresh, and make sure the change is permanent
            selenium.get(
                self.live_server_url
                + reverse("course_flow:workflow-update", args=[workflow.pk])
            )
            time.sleep(2)
            ActionChains(selenium).move_to_element(
                selenium.find_elements(By.CSS_SELECTOR, ".node .side-actions")[
                    1
                ]
            ).perform()
            self.assertEqual(
                len(
                    selenium.find_elements(
                        By.CSS_SELECTOR,
                        ".outcome-node .outcome-" + str(outcome.id),
                    )
                ),
                0,
            )
            ActionChains(selenium).move_to_element(
                selenium.find_elements(By.CSS_SELECTOR, ".node .side-actions")[
                    0
                ]
            ).perform()
            self.assertEqual(
                len(
                    selenium.find_elements(
                        By.CSS_SELECTOR,
                        ".outcome-node .outcome-" + str(child1.id),
                    )
                ),
                0,
            )

            # Restore the outcome
            selenium.find_element(
                By.CSS_SELECTOR, "a[href='#restore-bar'] span"
            ).click()
            selenium.find_element(
                By.CSS_SELECTOR,
                "#restore-bar-workflow .node-bar-column-block button",
            ).click()
            time.sleep(2)

            # Make sure the outcome was restored
            ActionChains(selenium).move_to_element(
                selenium.find_elements(By.CSS_SELECTOR, ".node .side-actions")[
                    1
                ]
            ).perform()
            self.assertEqual(
                len(
                    selenium.find_elements(
                        By.CSS_SELECTOR,
                        ".outcome-node .outcome-" + str(outcome.id),
                    )
                ),
                1,
            )
            ActionChains(selenium).move_to_element(
                selenium.find_elements(By.CSS_SELECTOR, ".node .side-actions")[
                    0
                ]
            ).perform()
            self.assertEqual(
                len(
                    selenium.find_elements(
                        By.CSS_SELECTOR,
                        ".outcome-node .outcome-" + str(child1.id),
                    )
                ),
                1,
            )

            # Refresh, and make sure the change is permanent
            selenium.get(
                self.live_server_url
                + reverse("course_flow:workflow-update", args=[workflow.pk])
            )
            time.sleep(2)
            ActionChains(selenium).move_to_element(
                selenium.find_elements(By.CSS_SELECTOR, ".node .side-actions")[
                    1
                ]
            ).perform()
            self.assertEqual(
                len(
                    selenium.find_elements(
                        By.CSS_SELECTOR,
                        ".outcome-node .outcome-" + str(outcome.id),
                    )
                ),
                1,
            )
            ActionChains(selenium).move_to_element(
                selenium.find_elements(By.CSS_SELECTOR, ".node .side-actions")[
                    0
                ]
            ).perform()
            self.assertEqual(
                len(
                    selenium.find_elements(
                        By.CSS_SELECTOR,
                        ".outcome-node .outcome-" + str(child1.id),
                    )
                ),
                1,
            )

    def test_delete_restore_workflow(self):
        print("\nIn method", self._testMethodName, ": ")
        selenium = self.selenium
        wait = WebDriverWait(selenium, timeout=10)
        project = Project.objects.create(author=self.user)
        course = Course.objects.create(author=self.user)
        program = Program.objects.create(author=self.user)
        WorkflowProject.objects.create(workflow=course, project=project)
        WorkflowProject.objects.create(workflow=program, project=project)
        program.weeks.first().nodes.create(
            author=self.user, column=program.columns.first(), node_type=2
        )
        Favourite.objects.create(
            user=self.user, content_object=course.get_workflow()
        )
        selenium.get(
            self.live_server_url
            + reverse("course_flow:project-update", args=[project.pk])
        )

        wait.until(
            EC.element_to_be_clickable(
                (By.CSS_SELECTOR, "[data-test-id='panel-favourite']")
            )
        )

        self.assertEqual(
            len(
                selenium.find_elements(
                    By.CSS_SELECTOR, "[data-test-id='panel-favourite']"
                )
            ),
            2,
        )

        # delete a workflow
        selenium.get(
            self.live_server_url
            + reverse("course_flow:workflow-update", args=[course.pk])
        )
        time.sleep(2)
        selenium.find_element(By.CSS_SELECTOR, "#overflow-options").click()
        selenium.find_element(By.CSS_SELECTOR, "#delete-workflow").click()
        alert = wait.until(expected_conditions.alert_is_present())
        selenium.switch_to.alert.accept()
        time.sleep(2)
        self.assertEqual(Course.objects.get(pk=course.pk).deleted, True)
        selenium.get(
            self.live_server_url
            + reverse("course_flow:project-update", args=[project.pk])
        )

        # make sure it doesn't show up in favourites
        time.sleep(1)
        self.assertEqual(
            len(
                selenium.find_elements(
                    By.CSS_SELECTOR, "[data-test-id='panel-favourite']"
                )
            ),
            0,
        )
        self.assertEqual(
            len(selenium.find_elements(By.CSS_SELECTOR, ".workflow-for-menu")),
            1,
        )

        # make sure it doesn't show up in linked wf
        selenium.get(
            self.live_server_url
            + reverse("course_flow:workflow-update", args=[program.pk])
        )
        time.sleep(2)
        ActionChains(selenium).move_to_element_with_offset(
            selenium.find_element(
                By.CSS_SELECTOR, ".workflow-details .node .node-title"
            ),
            5,
            5,
        ).click().perform()
        time.sleep(1)
        selenium.find_element(
            By.CSS_SELECTOR, "#linked-workflow-editor"
        ).click()
        time.sleep(3)
        self.assertEqual(
            len(
                selenium.find_elements(
                    By.CSS_SELECTOR, ".message-wrap .workflow-for-menu"
                )
            ),
            0,
        )

        # Restore
        selenium.get(
            self.live_server_url
            + reverse("course_flow:workflow-update", args=[course.pk])
        )
        time.sleep(1)
        time.sleep(2)
        selenium.find_element(By.CSS_SELECTOR, "#overflow-options").click()
        selenium.find_element(By.CSS_SELECTOR, "#restore-workflow").click()
        time.sleep(2)
        # make sure shows up in favourites

        selenium.get(
            self.live_server_url
            + reverse("course_flow:project-update", args=[project.pk])
        )
        time.sleep(1)
        self.assertEqual(
            len(selenium.find_elements(By.CSS_SELECTOR, ".panel-favourite")),
            1,
        )
        self.assertEqual(
            len(selenium.find_elements(By.CSS_SELECTOR, ".workflow-for-menu")),
            2,
        )

        # make sure it shows up in linked wf
        selenium.get(
            self.live_server_url
            + reverse("course_flow:workflow-update", args=[program.pk])
        )
        time.sleep(2)
        ActionChains(selenium).move_to_element_with_offset(
            selenium.find_element(
                By.CSS_SELECTOR, ".workflow-details .node .node-title"
            ),
            5,
            5,
        ).click().perform()
        time.sleep(1)
        selenium.find_element(
            By.CSS_SELECTOR, "#linked-workflow-editor"
        ).click()
        time.sleep(3)
        self.assertEqual(
            len(
                selenium.find_elements(
                    By.CSS_SELECTOR, ".message-wrap .workflow-for-menu"
                )
            ),
            1,
        )

    def test_explore_deleted(self):
        print("\nIn method", self._testMethodName, ": ")
        selenium = self.selenium

        wait = WebDriverWait(selenium, timeout=10)
        author = get_author()
        discipline = Discipline.objects.create(title="Discipline1")
        self.create_many_items(author, True, disciplines=[discipline])

        Workflow.objects.all().update(deleted=True)
        # make deleted workflows don't show up in explore
        selenium.get(self.live_server_url + reverse("course_flow:explore"))

        selenium.find_element(
            By.CSS_SELECTOR, "#workflow-search+button"
        ).click()
        time.sleep(1)
        self.assertEqual(
            len(selenium.find_elements(By.CSS_SELECTOR, ".page-button")), 1
        )
        self.assertEqual(
            len(selenium.find_elements(By.CSS_SELECTOR, ".workflow-title")), 10
        )
        Project.objects.all().update(deleted=True)
        # make deleted projects don't show up in explore
        selenium.get(self.live_server_url + reverse("course_flow:explore"))
        selenium.find_element(
            By.CSS_SELECTOR, "#workflow-search+button"
        ).click()
        time.sleep(1)
        self.assertEqual(
            len(selenium.find_elements(By.CSS_SELECTOR, ".page-button")), 0
        )
        self.assertEqual(
            len(selenium.find_elements(By.CSS_SELECTOR, ".workflow-title")), 0
        )
        Workflow.objects.all().update(deleted=False)
        # make workflows from deleted projects don't show up in explore
        selenium.get(self.live_server_url + reverse("course_flow:explore"))
        selenium.find_element(
            By.CSS_SELECTOR, "#workflow-search+button"
        ).click()
        time.sleep(1)
        self.assertEqual(
            len(selenium.find_elements(By.CSS_SELECTOR, ".page-button")), 0
        )
        self.assertEqual(
            len(selenium.find_elements(By.CSS_SELECTOR, ".workflow-title")), 0
        )


class SeleniumObjectSetsTestCase(ChannelsStaticLiveServerTestCase):
    def setUp(self):
        selbase = SeleniumBase()
        self.selenium = selbase.init_selenium()

        super().setUp()
        selenium = self.selenium
        selenium.maximize_window()

        self.user = login(self)
        selenium.get(self.live_server_url + "/course-flow/home/")
        username = selenium.find_element(By.ID, "id_username")
        password = selenium.find_element(By.ID, "id_password")
        username.send_keys("testuser1")
        password.send_keys("testpass1")
        selenium.find_element(By.CSS_SELECTOR, "button[type=Submit]").click()

    def tearDown(self):
        self.selenium.quit()
        super().tearDown()

    def test_create_sets(self):
        print("\nIn method", self._testMethodName, ": ")
        selenium = self.selenium
        wait = WebDriverWait(selenium, timeout=10)
        project = Project.objects.create(author=self.user)

        # Navigate to newly created project
        selenium.get(
            self.live_server_url
            + reverse("course_flow:project-update", args=[project.pk])
        )
        edit_projects_button = wait.until(
            EC.element_to_be_clickable((By.ID, "edit-project-button"))
        )
        edit_projects_button.click()

        nomenclature_select_button = wait.until(
            EC.element_to_be_clickable((By.ID, "nomenclature-select"))
        )
        nomenclature_select_button.click()

        selenium.find_element(
            By.CSS_SELECTOR,
            "#nomenclature-select option[value='program outcome']",
        ).click()
        selenium.find_element(By.CSS_SELECTOR, "#term-singular").send_keys(
            "competency"
        )
        selenium.find_element(
            By.CSS_SELECTOR, ".nomenclature-add-button"
        ).click()

        time.sleep(1)

        # test how many object sets are on project
        self.assertEqual(project.object_sets.count(), 1)

        # now delete the project set
        selenium.find_element(
            By.CSS_SELECTOR, ".nomenclature-delete-button"
        ).click()

        alert = wait.until(expected_conditions.alert_is_present())
        selenium.switch_to.alert.accept()

        time.sleep(2)
        self.assertEqual(project.object_sets.count(), 0)

    def test_view_sets(self):
        print("\nIn method", self._testMethodName, ": ")
        selenium = self.selenium
        wait = WebDriverWait(selenium, timeout=10)

        # create test data
        project = Project.objects.create(author=self.user)
        workflow = Program.objects.create(author=self.user)
        WorkflowProject.objects.create(workflow=workflow, project=project)
        node = workflow.weeks.first().nodes.create(
            author=self.user, column=workflow.columns.first(), node_type=2
        )
        outcome = workflow.outcomes.create(author=self.user)
        OutcomeNode.objects.create(outcome=outcome, node=node)
        nodeset = project.object_sets.create(
            term="program node", title="Nodes"
        )
        outcomeset = project.object_sets.create(
            term="program outcome", title="Outcomes"
        )
        outcome.sets.add(outcomeset)

        # navigate to project URL
        selenium.get(
            self.live_server_url
            + reverse("course_flow:workflow-update", args=[workflow.pk])
        )
        created_by_button = wait.until(
            EC.presence_of_all_elements_located((By.CSS_SELECTOR, ".node"))
        )
        self.assertEqual(len(created_by_button), 1)

        # hover over the side actions panel to show outcomes assigned to node
        ActionChains(selenium).move_to_element(
            selenium.find_elements(By.CSS_SELECTOR, ".node .side-actions")[0]
        ).perform()

        print(str(outcome.id))

        # @todo fails here, why would there be 2 outcome by ID when we only created one ?
        # test needs to be fixed (and some more verbose commenting would help)
        self.assertEqual(
            len(
                selenium.find_elements(
                    By.CSS_SELECTOR, ".outcome-" + str(outcome.id)
                )
            ),
            2,
        )

        selenium.find_element(By.CSS_SELECTOR, ".node").click()
        self.assertEqual(
            len(
                selenium.find_elements(
                    By.CSS_SELECTOR, "input[name='" + str(outcomeset.id) + "']"
                )
            ),
            0,
        )
        element = selenium.find_element(
            By.CSS_SELECTOR, "input[name='" + str(nodeset.id) + "']"
        )
        selenium.execute_script("arguments[0].scrollIntoView();", element)
        element.click()
        time.sleep(2)
        selenium.find_element(By.CSS_SELECTOR, "[href='#view-bar']").click()
        selenium.find_element(
            By.CSS_SELECTOR, "#set" + str(outcomeset.id)
        ).click()

        self.assertEqual(
            len(
                selenium.find_elements(
                    By.CSS_SELECTOR, ".outcome-" + str(outcome.id)
                )
            ),
            0,
        )
        selenium.find_element(
            By.CSS_SELECTOR, "#set" + str(nodeset.id)
        ).click()
        self.assertEqual(
            len(selenium.find_elements(By.CSS_SELECTOR, ".node")), 1
        )

        selenium.find_element(By.CSS_SELECTOR, "#button_outcomeedit").click()
        time.sleep(3)
        self.assertEqual(
            len(
                selenium.find_elements(
                    By.CSS_SELECTOR, ".outcome-" + str(outcome.id)
                )
            ),
            0,
        )

        selenium.find_element(By.CSS_SELECTOR, ".other-views").click()
        selenium.find_element(By.CSS_SELECTOR, "#button_outcometable").click()
        time.sleep(3)
        self.assertEqual(
            len(
                selenium.find_elements(
                    By.CSS_SELECTOR, ".outcome-" + str(outcome.id)
                )
            ),
            0,
        )
        self.assertEqual(
            len(selenium.find_elements(By.CSS_SELECTOR, ".node")), 0
        )
        selenium.find_element(By.CSS_SELECTOR, "#table_type_matrix").click()
        time.sleep(3)
        self.assertEqual(
            len(
                selenium.find_elements(
                    By.CSS_SELECTOR, ".outcome-" + str(outcome.id)
                )
            ),
            0,
        )
        self.assertEqual(
            len(selenium.find_elements(By.CSS_SELECTOR, ".node")), 0
        )
        selenium.find_element(By.CSS_SELECTOR, ".other-views").click()
        selenium.find_element(
            By.CSS_SELECTOR, "#button_alignmentanalysis"
        ).click()
        time.sleep(3)
        self.assertEqual(
            len(
                selenium.find_elements(
                    By.CSS_SELECTOR, ".outcome-" + str(outcome.id)
                )
            ),
            0,
        )
        self.assertEqual(
            len(selenium.find_elements(By.CSS_SELECTOR, ".node")), 0
        )

        selenium.find_element(By.CSS_SELECTOR, ".other-views").click()
        selenium.find_element(By.CSS_SELECTOR, "#button_grid").click()
        time.sleep(3)
        self.assertEqual(
            len(
                selenium.find_elements(
                    By.CSS_SELECTOR, ".outcome-" + str(outcome.id)
                )
            ),
            0,
        )
        self.assertEqual(
            len(selenium.find_elements(By.CSS_SELECTOR, ".node")), 0
        )


class ComparisonViewTestCase(ChannelsStaticLiveServerTestCase):
    def setUp(self):
        selbase = SeleniumBase()
        self.selenium = selbase.init_selenium()

        super().setUp()
        selenium = self.selenium
        selenium.maximize_window()

        self.user = login(self)
        selenium.get(self.live_server_url + "/course-flow/home/")
        username = selenium.find_element(By.ID, "id_username")
        password = selenium.find_element(By.ID, "id_password")

        username.send_keys("testuser1")
        password.send_keys("testpass1")
        selenium.find_element(By.CSS_SELECTOR, "button[type=Submit]").click()

    def tearDown(self):
        self.selenium.quit()
        super().tearDown()

    def test_comparison_views(self):
        print("In method", self._testMethodName, ": ")
        selenium = self.selenium
        wait = WebDriverWait(selenium, timeout=10)

        # create a project and add 2 courses to it
        project = Project.objects.create(author=self.user)
        workflow = Course.objects.create(author=self.user)
        workflow2 = Course.objects.create(author=self.user)
        WorkflowProject.objects.create(workflow=workflow, project=project)
        WorkflowProject.objects.create(workflow=workflow2, project=project)

        # get the just created project's URL
        selenium.get(
            self.live_server_url
            + reverse("course_flow:project-update", args=[project.pk])
        )

        # click the more icon ( three dots)
        more_icon = wait.until(
            EC.element_to_be_clickable((By.ID, "overflow-options"))
        )
        more_icon.click()

        # click the "workflow comparison tool" menu item
        workflow_comparison_tool_button = wait.until(
            EC.element_to_be_clickable((By.ID, "comparison-view"))
        )
        workflow_comparison_tool_button.click()

        # click the "load new workflow" button
        load_new_workflow_button = wait.until(
            EC.element_to_be_clickable((By.ID, "load-workflow"))
        )
        load_new_workflow_button.click()

        # click the "created by" button (selects card)
        # using presence_of_all_elements_located but could move this back to element_to_be_clickable if problems
        created_by_button = wait.until(
            EC.presence_of_all_elements_located(
                (By.CSS_SELECTOR, ".message-wrap .workflow-for-menu")
            )
        )
        created_by_button[0].click()

        print(created_by_button)

        # click select button
        select_button = wait.until(
            EC.element_to_be_clickable((By.ID, "set-linked-workflow"))
        )
        select_button.click()

        workflow_menu = wait.until(
            EC.presence_of_all_elements_located(
                (By.CSS_SELECTOR, ".workflow-wrapper .workflow-for-menu")
            )
        )

        # can we see one course box?
        self.assertEqual(len(workflow_menu), 1)

        # load a different workflow
        load_workflow_button = wait.until(
            EC.element_to_be_clickable((By.ID, "load-workflow"))
        )
        load_workflow_button.click()

        # click the "created by" button (selects card), get the 2nd element this time
        created_by_button = wait.until(
            EC.presence_of_all_elements_located(
                (By.CSS_SELECTOR, ".message-wrap .workflow-created")
            )
        )
        created_by_button[1].click()

        select_button = wait.until(
            EC.element_to_be_clickable((By.ID, "set-linked-workflow"))
        )
        select_button.click()

        # can we see two course boxes?
        workflow_menu = wait.until(
            EC.presence_of_all_elements_located(
                (By.CSS_SELECTOR, ".workflow-wrapper .workflow-for-menu")
            )
        )
        self.assertEqual(len(workflow_menu), 2)

        outcome_button = wait.until(
            EC.element_to_be_clickable((By.ID, "button_outcomeedit"))
        )
        outcome_button.click()

        # @todo this is still failing, not sure why we have expected outcomes here
        outcomes = wait.until(
            EC.presence_of_all_elements_located((By.CSS_SELECTOR, ".outcome"))
        )
        self.assertEqual(len(outcomes), 2)


class WebsocketTestCase(ChannelsStaticLiveServerTestCase):
    def setUp(self):
        selbase = SeleniumBase()
        self.selenium = selbase.init_selenium()

        super().setUp()
        selenium = self.selenium
        selenium.maximize_window()

        self.user = login(self)
        selenium.get(self.live_server_url + "/course-flow/home/")
        username = selenium.find_element(By.ID, "id_username")
        password = selenium.find_element(By.ID, "id_password")
        username.send_keys("testuser1")
        password.send_keys("testpass1")
        selenium.find_element(By.CSS_SELECTOR, "button[type=Submit]").click()

    def tearDown(self):
        self.selenium.quit()
        super().tearDown()

    def test_permissions_connect_to_workflow_update_consumer(self):
        print("In method", self._testMethodName, "\n")
        author = get_author()
        user = self.user
        workflow_owned = Course.objects.create(author=user)
        workflow_view = Course.objects.create(author=author)
        workflow_edit = Course.objects.create(author=author)
        workflow_published = Course.objects.create(
            author=author, published=True
        )
        workflow_none = Course.objects.create(author=author)
        ObjectPermission.objects.create(
            user=user,
            content_object=workflow_view,
            permission_type=ObjectPermission.PERMISSION_VIEW,
        )
        ObjectPermission.objects.create(
            user=user,
            content_object=workflow_edit,
            permission_type=ObjectPermission.PERMISSION_EDIT,
        )

        application = URLRouter(websocket_urlpatterns)
        headers = [
            (b"origin", b"..."),
            (
                b"cookie",
                self.client.cookies.output(header="", sep="; ").encode(),
            ),
        ]

        url = "/ws/update/" + str(workflow_owned.pk) + "/"
        communicator = WebsocketCommunicator(application, url, headers)
        communicator.scope["user"] = user
        connected, subprotocol = async_to_sync_connect(communicator)
        assert connected
        my_input = async_to_sync_send_input(
            communicator, {"type": "micro_update", "action": "my_action"}
        )
        output = json.loads(async_to_sync_receive_output(communicator))
        self.assertEqual(output["action"], "my_action")
        async_to_sync_disconnect(communicator)

        url = "/ws/update/" + str(workflow_view.pk) + "/"
        communicator = WebsocketCommunicator(application, url, headers)
        communicator.scope["user"] = user
        connected, subprotocol = async_to_sync_connect(communicator)
        assert connected
        my_input = async_to_sync_send_input(
            communicator, {"type": "micro_update", "action": "my_action"}
        )
        output = async_to_sync_receive_nothing(communicator)
        assert output
        async_to_sync_disconnect(communicator)

        url = "/ws/update/" + str(workflow_edit.pk) + "/"
        communicator = WebsocketCommunicator(application, url, headers)
        communicator.scope["user"] = user
        connected, subprotocol = async_to_sync_connect(communicator)
        assert connected
        my_input = async_to_sync_send_input(
            communicator, {"type": "micro_update", "action": "my_action"}
        )
        output = json.loads(async_to_sync_receive_output(communicator))
        self.assertEqual(output["action"], "my_action")
        async_to_sync_disconnect(communicator)

        url = "/ws/update/" + str(workflow_published.pk) + "/"
        communicator = WebsocketCommunicator(application, url, headers)
        communicator.scope["user"] = user
        connected, subprotocol = async_to_sync_connect(communicator)
        assert connected
        my_input = async_to_sync_send_input(
            communicator, {"type": "micro_update", "action": "my_action"}
        )
        output = async_to_sync_receive_nothing(communicator)
        assert output
        async_to_sync_disconnect(communicator)

        url = "/ws/update/" + str(workflow_none.pk) + "/"
        communicator = WebsocketCommunicator(application, url, headers)
        communicator.scope["user"] = user
        connected, subprotocol = async_to_sync_connect(communicator)
        assert not connected

    def test_connection_bar(self):
        print("In method", self._testMethodName, "\n")
        selenium = self.selenium
        wait = WebDriverWait(selenium, timeout=10)

        author = get_author()
        user = self.user
        workflow_edit = Course.objects.create(author=author).get_workflow()
        workflow_published = Course.objects.create(
            author=author
        ).get_workflow()
        project = Project.objects.create(author=author)
        WorkflowProject.objects.create(project=project, workflow=workflow_edit)
        WorkflowProject.objects.create(
            project=project, workflow=workflow_published
        )
        ObjectPermission.objects.create(
            user=user,
            content_object=workflow_edit,
            permission_type=ObjectPermission.PERMISSION_EDIT,
        )

        selenium.get(
            self.live_server_url
            + reverse("course_flow:workflow-update", args=[workflow_edit.pk])
        )
        time.sleep(3)
        self.assertEqual(
            len(
                selenium.find_elements(
                    By.CSS_SELECTOR, ".users-box .users-small .user-indicator"
                )
            ),
            1,
        )
        workflow_published.published = True
        workflow_published.save()
        project.published = True
        project.save()
        selenium.get(
            self.live_server_url
            + reverse(
                "course_flow:workflow-update", args=[workflow_published.pk]
            )
        )
        time.sleep(3)
        self.assertEqual(
            len(
                selenium.find_elements(
                    By.CSS_SELECTOR, ".users-box .users-small .user-indicator"
                )
            ),
            0,
        )
        selenium.close()


########################
# HELPERS
########################


def action_hover_click(selenium, hover_item, click_item):
    hover = (
        ActionChains(selenium).move_to_element(hover_item).click(click_item)
    )
    return hover


async def connect_ws(ws):
    return await ws.connect()


async def disconnect_ws(ws):
    return await ws.disconnect()


async def send_input_ws(ws, data):
    return await ws.send_json_to(data)


async def receive_output_ws(ws):
    return await ws.receive_from(timeout=1)


async def receive_nothing_ws(ws):
    return await ws.receive_nothing(timeout=1)


def async_to_sync_connect(ws):
    loop = asyncio.get_event_loop()
    coroutine = connect_ws(ws)
    return loop.run_until_complete(coroutine)


def async_to_sync_disconnect(ws):
    loop = asyncio.get_event_loop()
    coroutine = disconnect_ws(ws)
    return loop.run_until_complete(coroutine)


def async_to_sync_send_input(ws, data):
    loop = asyncio.get_event_loop()
    coroutine = send_input_ws(ws, data)
    return loop.run_until_complete(coroutine)


def async_to_sync_receive_output(ws):
    loop = asyncio.get_event_loop()
    coroutine = receive_output_ws(ws)
    return loop.run_until_complete(coroutine)


def async_to_sync_receive_nothing(ws):
    loop = asyncio.get_event_loop()
    coroutine = receive_nothing_ws(ws)
    return loop.run_until_complete(coroutine)
