diff --git a/.github/workflows/django-tests.yml b/.github/workflows/django-tests.yml index 52bbe2cc..6c717697 100644 --- a/.github/workflows/django-tests.yml +++ b/.github/workflows/django-tests.yml @@ -109,6 +109,8 @@ jobs: - name: Integration tests run: | cd ${{ matrix.app }} + playwright install + export DJANGO_ALLOW_ASYNC_UNSAFE=1 make testint - uses: actions/upload-artifact@v1 if: failure() @@ -165,7 +167,9 @@ jobs: - name: Integration tests run: | cd integration_tests - pytest --headless2 + playwright install + export DJANGO_ALLOW_ASYNC_UNSAFE=1 + pytest - uses: actions/upload-artifact@v1 if: failure() with: diff --git a/integration_tests/conftest.py b/integration_tests/conftest.py index 66907da9..e120148c 100644 --- a/integration_tests/conftest.py +++ b/integration_tests/conftest.py @@ -13,10 +13,11 @@ import django import pytest -from seleniumbase.common.exceptions import NoSuchElementException from lab_settings import EMAIL_FILE_PATH +from playwright.sync_api import Page, Browser + LAB_PORT = 18000 PARENT_PORT = 19000 @@ -138,23 +139,23 @@ def apps(lab_app, parent_app): return namedtuple("Apps", "lab,parent")(lab_app, parent_app) +def set_language_english(page): + loc = page.locator("button").get_by_text("English") + if loc.count(): + loc.click() + + @pytest.fixture -def as_admin(sb, lab_app): +def as_admin(browser: Browser, lab_app): lab_app.load("admin") - driver = sb.get_new_driver() - sb.switch_to_driver(driver) - sb.open(lab_app.url + '/login') - sb.type("#id_username", "admin") - sb.type("#id_password", "admin") - sb.click('button:contains("Log in")') - try: - sb.click('button:contains("English")') - except NoSuchElementException: - # ugly workaround for language setting - pass - - sb.switch_to_default_driver() - return driver + context = browser.new_context() + page_admin = context.new_page() + page_admin.goto(lab_app.url + '/login') + set_language_english(page_admin) + page_admin.fill("#id_username", "admin") + page_admin.fill("#id_password", "admin") + page_admin.locator('button').get_by_text("Log in").click() + return page_admin def read_mail(address): @@ -197,17 +198,16 @@ def _delegate(email, subject=None): @pytest.fixture -def login_as(sb, apps, link_from_mail, mailbox): +def login_as(page: Page, apps, link_from_mail, mailbox): def _delegate(email): - sb.switch_to_default_driver() - sb.open(apps.parent.url + 'auth/') - sb.type('input[name="email"]', email) - sb.click('button:contains("Send")') + page.goto(apps.parent.url + 'auth/') + page.fill('input[name="email"]', email) + page.locator('button').get_by_text('Send').click() # use login link from (second) email if link := link_from_mail(email, 'Link'): - sb.open(link) + page.goto(link) return True return False diff --git a/integration_tests/test_invite.py b/integration_tests/test_invite.py index dc9babb4..50266814 100644 --- a/integration_tests/test_invite.py +++ b/integration_tests/test_invite.py @@ -6,6 +6,8 @@ import pytest +from playwright.sync_api import expect + @pytest.fixture def participant(apps): @@ -25,7 +27,7 @@ def participant(apps): participant.delete() -def test_cancel_appointment_from_email(apps, participant, mailbox, link_from_mail, sb): +def test_cancel_appointment_from_email(apps, participant, mailbox, link_from_mail, page): apps.lab.load('admin') # generate admin user Experiment = apps.lab.get_model('experiments', 'Experiment') DefaultCriteria = apps.lab.get_model('experiments', 'DefaultCriteria') @@ -46,7 +48,7 @@ def test_cancel_appointment_from_email(apps, participant, mailbox, link_from_mai send_appointment_mail(appointment, prepare_appointment_mail(appointment)) - sb.open(link_from_mail(participant.email)) + page.goto(link_from_mail(participant.email)) # check that the appointment was canceled appointment.refresh_from_db() @@ -64,13 +66,13 @@ def test_cancel_appointment_from_email(apps, participant, mailbox, link_from_mai appointment.delete() -def test_appointment_in_parent_overview(apps, participant, mailbox, sb, login_as): +def test_appointment_in_parent_overview(apps, participant, mailbox, page, login_as): apps.lab.load('admin') # generate admin user Experiment = apps.lab.get_model('experiments', 'Experiment') DefaultCriteria = apps.lab.get_model('experiments', 'DefaultCriteria') User = apps.lab.get_model('main', 'User') experiment = Experiment.objects.create(defaultcriteria=DefaultCriteria.objects.create(), - name='Text Experiment') + name='Test Experiment') # somewhat abusing the get_model() calls above to setup django for the following to work from experiments.models import make_appointment @@ -84,21 +86,21 @@ def test_appointment_in_parent_overview(apps, participant, mailbox, sb, login_as login_as(participant.email) try: - sb.assert_text_visible('Appointments') - sb.assert_text_visible(experiment.name) - sb.assert_text_visible(leader.name) + expect(page.get_by_text('Appointments')).to_be_visible() + expect(page.get_by_text(experiment.name)).to_be_visible() + expect(page.get_by_text(leader.name)).to_be_visible() finally: # delete appointment so that the participant can be deleted as well appointment.delete() -def test_past_appointment_not_in_parent_overview(apps, participant, mailbox, sb, login_as): +def test_past_appointment_not_in_parent_overview(apps, participant, mailbox, page, login_as): apps.lab.load('admin') # generate admin user Experiment = apps.lab.get_model('experiments', 'Experiment') DefaultCriteria = apps.lab.get_model('experiments', 'DefaultCriteria') User = apps.lab.get_model('main', 'User') experiment = Experiment.objects.create(defaultcriteria=DefaultCriteria.objects.create(), - name='Text Experiment') + name='Test Experiment') # somewhat abusing the get_model() calls above to setup django for the following to work from experiments.models import make_appointment @@ -112,9 +114,9 @@ def test_past_appointment_not_in_parent_overview(apps, participant, mailbox, sb, login_as(participant.email) try: - sb.assert_text_visible('Appointments') - sb.assert_text_not_visible(experiment.name) - sb.assert_text_not_visible(leader.name) + expect(page.get_by_text('Appointments')).to_be_visible() + expect(page.get_by_text(experiment.name)).not_to_be_visible() + expect(page.get_by_text(leader.name)).not_to_be_visible() finally: # delete appointment so that the participant can be deleted as well appointment.delete() diff --git a/integration_tests/test_parent.py b/integration_tests/test_parent.py index f8a32f35..f10b4910 100644 --- a/integration_tests/test_parent.py +++ b/integration_tests/test_parent.py @@ -3,85 +3,82 @@ import string import pytest -from selenium.webdriver.common.keys import Keys from django.utils import timezone +from playwright.sync_api import Page, expect @pytest.fixture -def default_signup_fill_form(sb, apps): +def default_signup_fill_form(page: Page, apps): """opens the signup form and fills some default values. individual tests should overwrite as necessary""" - sb.open(apps.parent.url + 'signup/') + page.goto(apps.parent.url + 'signup/') - sb.type('#id_name', 'Test Baby') - sb.click('#id_sex_0') + page.fill('#id_name', 'Test Baby') + page.click('#id_sex_0') - sb.select_option_by_text('#id_birth_date_month', 'March') - sb.select_option_by_text('#id_birth_date_day', '5') - sb.select_option_by_text('#id_birth_date_year', str(datetime.datetime.now().year - 1)) + page.locator('#id_birth_date_month').select_option('March') + page.locator('#id_birth_date_day').select_option('5') + page.locator('#id_birth_date_year').select_option(str(datetime.datetime.now().year - 1)) - sb.click('#id_birth_weight_1') - sb.click('#id_pregnancy_duration_1') - sb.click('#id_dyslexic_parent_3') - sb.click('#id_tos_parent_0') - sb.click('#id_languages_mono_dutch') + page.click('#id_birth_weight_1') + page.click('#id_pregnancy_duration_1') + page.click('#id_dyslexic_parent_3') + page.click('#id_tos_parent_0') + page.click('#id_languages_mono_dutch') - sb.type('#id_parent_first_name', 'Test') - sb.type('#id_parent_last_name', 'Parent') - sb.type('#id_phonenumber', '06412345678') - sb.type('#id_phonenumber_alt', '06487654321') + page.fill('#id_parent_first_name', 'Test') + page.fill('#id_parent_last_name', 'Parent') + page.fill('#id_phonenumber', '06412345678') + page.fill('#id_phonenumber_alt', '06487654321') - sb.type('#id_email', 'parent@example.com') - sb.type('#id_email_again', 'parent@example.com') + page.fill('#id_email', 'parent@example.com') + page.fill('#id_email_again', 'parent@example.com') - sb.click('#id_save_longer_0') - sb.click('#id_english_contact_0') - sb.click('#id_newsletter_0') - # use js_click instead of click, because if these are not visible (below view), - # then a normal click will sometimes not work - sb.js_click('#id_data_consent_0') + page.click('#id_save_longer_0') + page.click('#id_english_contact_0') + page.click('#id_newsletter_0') + page.click('#id_data_consent_0') @pytest.fixture -def signup(sb, apps, default_signup_fill_form): +def signup(page: Page, apps, default_signup_fill_form): suffix = ''.join(random.choice(string.digits) for i in range(4)) email = f'parent{suffix}@localhost.local' - sb.type('#id_email', email) - sb.type('#id_email_again', email) - sb.click('input[type="submit"]') + page.fill('#id_email', email) + page.fill('#id_email_again', email) + page.click('input[type="submit"]') # check that the form was submitted - sb.assert_element_not_visible('input[type="submit"]') - assert 'signup/done' in sb.get_current_url() + expect(page.locator('input[type="submit"]')).not_to_be_visible() + assert 'signup/done' in page.url return email -def test_parent_login(sb, apps, signup, as_admin, link_from_mail, login_as): +def test_parent_login(page, apps, signup, as_admin, link_from_mail, login_as): # confirm signup email if link := link_from_mail(signup, 'Bevestiging'): - sb.open(link) + page.goto(link) else: pytest.fail('could not find validation link') - sb.switch_to_driver(as_admin) # approve signup - sb.click("a:contains(Participants)") - sb.click("a:contains(Signups)") - sb.click("tr:contains(details) a") - sb.click("button:contains(Approve)") + as_admin.get_by_role("button", name="Participants").click() + as_admin.locator("a").get_by_text("Signups").click() + as_admin.locator("a").get_by_text("details").click() + as_admin.locator("button").get_by_text("Approve").click() # try to login via email login_as(signup) # check that login worked - sb.assert_text_visible('Appointments') + expect(page.get_by_text('Appointments')).to_be_visible() -def test_parent_login_unapproved(signup, apps, sb, link_from_mail, login_as): +def test_parent_login_unapproved(signup, apps, page, link_from_mail, login_as): # confirm signup email if link := link_from_mail(signup, 'Bevestiging'): - sb.open(link) + page.goto(link) else: pytest.fail('could not find validation link') @@ -89,75 +86,73 @@ def test_parent_login_unapproved(signup, apps, sb, link_from_mail, login_as): assert login_as(signup) is False -def test_parent_self_deactivate(sb, participant, login_as): +def test_parent_self_deactivate(page, participant, login_as): login_as(participant.email) - sb.click('a:contains("Data management")') - # using sb.click directly interferes with the confirm() dialog - # so it's better to use find_element().click() - sb.find_element('button:contains("Remove from")').click() - sb.accept_alert() - sb.wait_for_element_not_visible('button:contains("Remove from")') + page.get_by_text("Data management").click() + page.on("dialog", lambda dialog: dialog.accept()) + page.get_by_text("Remove from").click() + page.get_by_text('Remove from').wait_for(state="hidden") participant.refresh_from_db() assert participant.deactivated -def test_parent_login_deactivated(participant, apps, sb, link_from_mail, login_as): +def test_parent_login_deactivated(participant, apps, page, link_from_mail, login_as): email = participant.email participant.deactivate() assert login_as(email) is False -def test_signup_no_english(sb, apps, default_signup_fill_form): +def test_signup_no_english(page, apps, default_signup_fill_form): Signup = apps.lab.get_model('signups', 'Signup') - sb.click('#id_english_contact_1') - sb.click('input[type="submit"]') + page.click('#id_english_contact_1') + page.click('input[type="submit"]') # check that the form was submitted - sb.assert_element_not_visible('input[type="submit"]') - assert 'signup/done' in sb.get_current_url() + expect(page.locator('input[type="submit"]')).not_to_be_visible() + assert 'signup/done' in page.url signup = Signup.objects.last() # verify the fields were saved correctly assert signup.english_contact is False -def test_signup_monolingual_dutch(sb, apps, default_signup_fill_form): +def test_signup_monolingual_dutch(page, apps, default_signup_fill_form): Signup = apps.lab.get_model('signups', 'Signup') - sb.click('#id_languages_mono_dutch') - sb.click('input[type="submit"]') + page.click('#id_languages_mono_dutch') + page.click('input[type="submit"]') # check that the form was submitted - sb.assert_element_not_visible('input[type="submit"]') - assert 'signup/done' in sb.get_current_url() + expect(page.locator('input[type="submit"]')).not_to_be_visible() + assert 'signup/done' in page.url signup = Signup.objects.last() # verify the fields were saved correctly assert signup.languages == ['Nederlands'] -def test_signup_multilingual_with_custom(sb, apps, default_signup_fill_form, as_admin): +def test_signup_multilingual_with_custom(page, apps, default_signup_fill_form, as_admin): Signup = apps.lab.get_model('signups', 'Signup') Participant = apps.lab.get_model('participants', 'Participant') - sb.click('#id_languages_multi') + page.click('#id_languages_multi') # select one language that's already on the list of known languages - sb.select_option_by_text('#id_languages_multi_select', 'Engels') + page.locator('#id_languages_multi_select').select_option('Engels') # type in one new language search_field = 'input.select2-search__field' - sb.click(search_field) - sb.send_keys(search_field, 'Afrikaans') - sb.send_keys(search_field, Keys.RETURN) + page.click(search_field) + page.type(search_field, 'Afrikaans') + page.keyboard.press('Enter') - sb.click('input[type="submit"]') + page.click('input[type="submit"]') # check that the form was submitted - sb.assert_element_not_visible('input[type="submit"]') - assert 'signup/done' in sb.get_current_url() + expect(page.locator('input[type="submit"]')).not_to_be_visible() + assert 'signup/done' in page.url signup = Signup.objects.last() # verify the fields were saved correctly @@ -166,41 +161,40 @@ def test_signup_multilingual_with_custom(sb, apps, default_signup_fill_form, as_ # approve signup signup.email_verified = timezone.now() signup.save() - sb.switch_to_driver(as_admin) - sb.click("a:contains(Participants)") - sb.click("a:contains(Signups)") - sb.click("tr:contains(details) a") - sb.click("button:contains(Approve)") + as_admin.get_by_role("button", name="Participants").click() + as_admin.locator("a").get_by_text("Signups").click() + as_admin.locator("a").get_by_text("details").last.click() + as_admin.locator("button").get_by_text("Approve").click() # check that the languages were saved correctly participant = Participant.objects.last() assert participant.languages.values_list('name', flat=True) -def test_signup_save_longer(sb, apps, default_signup_fill_form): +def test_signup_save_longer(page, apps, default_signup_fill_form): Signup = apps.lab.get_model('signups', 'Signup') - sb.click('#id_save_longer_0') - sb.click('input[type="submit"]') + page.click('#id_save_longer_0') + page.click('input[type="submit"]') # check that the form was submitted - sb.assert_element_not_visible('input[type="submit"]') - assert 'signup/done' in sb.get_current_url() + expect(page.locator('input[type="submit"]')).not_to_be_visible() + assert 'signup/done' in page.url signup = Signup.objects.last() # verify the fields were saved correctly assert signup.save_longer is True -def test_signup_unborn(sb, apps, default_signup_fill_form): +def test_signup_unborn(page, apps, default_signup_fill_form): Signup = apps.lab.get_model('signups', 'Signup') now = datetime.datetime.now() - sb.select_option_by_text('#id_birth_date_year', str(now.year)) - sb.select_option_by_text('#id_birth_date_month', now.strftime('%B')) - sb.select_option_by_text('#id_birth_date_day', str(now.day + 1)) + page.locator('#id_birth_date_year').select_option(str(now.year)) + page.locator('#id_birth_date_month').select_option(now.strftime('%B')) + page.locator('#id_birth_date_day').select_option(str(now.day + 1)) - sb.click('input[type="submit"]') + page.click('input[type="submit"]') # check that the form was submitted - sb.assert_element_visible('select.is-invalid') + assert page.locator('select.is-invalid').count() == 3 diff --git a/integration_tests/test_survey.py b/integration_tests/test_survey.py deleted file mode 100644 index c8fe5f7e..00000000 --- a/integration_tests/test_survey.py +++ /dev/null @@ -1,183 +0,0 @@ -import re -import random -import string -import time -from datetime import date - -import pytest - - -@pytest.fixture -def participant(apps): - suffix = ''.join(random.choice(string.digits) for i in range(4)) - Participant = apps.lab.get_model("participants", "Participant") - participant = Participant.objects.create( - email=f"baby{suffix}@baby.com", - name="Baby McBaby", - parent_first_name="Parent", - parent_last_name="McParent", - birth_date=date(2020, 1, 1), - phonenumber="987654321", - dyslexic_parent=Participant.WhichParent.UNKNOWN, - email_subscription=True, - ) - yield participant - participant.delete() - - -@pytest.fixture(scope='module') -def survey(apps): - SurveyDefinition = apps.lab.get_model("survey_admin", "SurveyDefinition") - survey_def = { - "pages": [ - {"intro": "first page", "questions": [{"template": "yesno", "prompt": "this is a test question"}]}, - {"intro": "second page", "questions": [{"template": "yesno", "prompt": "this is another test question"}]} - ] - } - sd = SurveyDefinition.objects.create(name="Test Survey", content=survey_def) - yield sd - sd.surveyinvite_set.all().delete() - sd.delete() - - -@pytest.mark.skip() -def test_survey_invite(sb, apps, as_admin, participant, survey, mailbox): - # send survey invite as admin - sb.switch_to_driver(as_admin) - sb.click("a:contains(Surveys)") - sb.click("button.dropdown-toggle") - sb.click("a:contains(invite)") - sb.click('input[type="checkbox"]') - sb.click("button:contains(send)") - - # read survey invite mail - mail = mailbox(participant.email) - assert len(mail) - html = mail[0].get_payload()[1].get_payload() - # find link in email - # it should authenticate us an redirect directly to the survey - link = re.search(r' (tinymce.activeEditor && tinymce.activeEditor.getContent())"): time.sleep(0.2) - sb.click("button:contains(Send)") - sb.wait_for_element_not_visible('button:contains("Send")') + page.locator("button").get_by_text("Send").click() + page.get_by_role("button", name="Send").wait_for(state="hidden") # baby mcbaby shouldn't be available anymore - sb.click("a:contains(Experiments)") - sb.click("a:contains(Overview)") - sb.click("button.icon-menu") - sb.click("a:contains(Invite)") - sb.assert_text_not_visible("Baby McBaby") + page.get_by_role("button", name="Experiments").click() + page.get_by_role("link", name="Overview").click() + page.click("button.icon-menu") + page.locator("a").get_by_text("Invite").click() + expect(page.get_by_text("Baby McBaby")).not_to_be_visible() # check that appointment is visible on agenda - sb.click("a:contains(Agenda)") - sb.assert_element(f'td[data-date="{tomorrow}"] .fc-event') + page.locator("a").get_by_text("Agenda").click() + expect(page.locator(f'td[data-date="{tomorrow}"] .fc-event')).to_be_visible() # appointment should contain both participant and leader names - sb.assert_text("Baby McBaby", ".fc-event") - sb.assert_text("Leader McLeader", ".fc-event") + expect(page.locator(".fc-event").get_by_text("Baby McBaby")).to_be_visible() + expect(page.locator(".fc-event").get_by_text("Leader McLeader")).to_be_visible() # check that a confirmation email was sent - sb.assertEqual(len(mail.outbox), 1) - sb.assertEqual(mail.outbox[0].to[0], sample_participant.email) + assert len(mail.outbox) == 1 + assert mail.outbox[0].to[0] == sample_participant.email # check that the attachment is present assert mail.outbox[0].attachments[0] == ("test-file", test_file_content, "text/plain") # check that at least parent name and leader name are in the email contents - sb.assertIn(sample_participant.parent_last_name, mail.outbox[0].body) - sb.assertIn(sample_leader.name, mail.outbox[0].body) - sb.assertIn(sample_participant.parent_last_name, mail.outbox[0].alternatives[0][0]) - sb.assertIn(sample_leader.name, mail.outbox[0].alternatives[0][0]) + assert sample_participant.parent_last_name in mail.outbox[0].body + assert sample_leader.name in mail.outbox[0].body + assert sample_participant.parent_last_name in mail.outbox[0].alternatives[0][0] + assert sample_leader.name in mail.outbox[0].alternatives[0][0] -def test_schedule_appointment_edit_email(sb, sample_experiment, sample_participant, sample_leader, as_leader): +def test_schedule_appointment_edit_email(page, sample_experiment, sample_participant, sample_leader, as_leader): sample_experiment.leaders.add(as_leader) sample_experiment.leaders.add(sample_leader) - sb.click("a:contains(Experiments)") - sb.click("a:contains(Overview)") - sb.click("button.icon-menu") - sb.click("a:contains(Invite)") - sb.click("td.actions a:contains(Call)") - sb.click("button:contains(Schedule)") + page.get_by_role("button", name="Experiments").click() + page.get_by_role("link", name="Overview").click() + page.click("button.icon-menu") + page.locator("a").get_by_text("Invite").click() + page.locator("td.actions").get_by_text("Call").click() + page.locator("button").get_by_text("Schedule").click() # pick time tomorrow = date.today() + timedelta(days=1) - sb.click(f'td[data-date="{tomorrow}"]') - sb.click('td.fc-timegrid-slot-lane[data-time="10:00:00"]') - sb.click("button:contains(Next)") + page.click(f'td[data-date="{tomorrow}"]') + page.click('td.fc-timegrid-slot-lane[data-time="10:00:00"]') + page.locator("button").get_by_text("Next").click() # pick leader - sb.select_option_by_text(".modal-content select", "Leader McLeader") - sb.click("button:contains(Confirm)") + page.locator(".modal-content select").select_option("Leader McLeader") + page.locator("button").get_by_text("Confirm").click() - while not sb.execute_script("return (tinymce.activeEditor && tinymce.activeEditor.getContent())"): + while not page.evaluate("() => (tinymce.activeEditor && tinymce.activeEditor.getContent())"): time.sleep(0.2) # set tinymce editor with a custom email string test_email = "this is a test email" test_email_plain = "this is a test email" - sb.execute_script('tinymce.activeEditor.setContent("{}")'.format(test_email)) + page.evaluate('() => tinymce.activeEditor.setContent("{}")'.format(test_email)) - sb.assertEqual(len(mail.outbox), 0) - sb.click("button:contains(Send)") - sb.wait_for_element_not_visible('button:contains("Send")') + assert len(mail.outbox) == 0 + page.locator("button").get_by_text("Send").click() + page.get_by_role("button", name="Send").wait_for(state="hidden") # check the email was sent - sb.assertEqual(len(mail.outbox), 1) - sb.assertEqual(mail.outbox[0].to[0], sample_participant.email) - sb.assertIn(test_email_plain, mail.outbox[0].body) - sb.assertIn(test_email, mail.outbox[0].alternatives[0][0]) + assert len(mail.outbox) == 1 + assert mail.outbox[0].to[0] == sample_participant.email + assert test_email_plain in mail.outbox[0].body + assert test_email in mail.outbox[0].alternatives[0][0] -def test_call_exclusion(sb, sample_experiment, sample_participant, sample_leader, as_admin): +def test_call_exclusion(page, sample_experiment, sample_participant, sample_leader, as_admin): sample_experiment.leaders.add(sample_leader) - sb.click("a:contains(Experiments)") - sb.click("a:contains(Overview)") - sb.click("button.icon-menu") - sb.click("a:contains(Invite)") - sb.click("td.actions a:contains(Call)") + page.get_by_role("button", name="Experiments").click() + page.get_by_role("link", name="Overview").click() + page.click("button.icon-menu") + page.locator("a").get_by_text("Invite").click() + page.locator("td.actions").get_by_text("Call").click() # indicates participant can't participate - sb.click('input[value="EXCLUDE"]') - sb.click("button:contains(Save)") + page.click('input[value="EXCLUDE"]') + page.locator("button").get_by_text("Save").click() # baby mcbaby shouldn't be available anymore - sb.click("a:contains(Experiments)") - sb.click("a:contains(Overview)") - sb.click("button.icon-menu") - sb.click("a:contains(Invite)") - sb.assert_text_not_visible("Baby McBaby") + page.get_by_role("button", name="Experiments").click() + page.get_by_role("link", name="Overview").click() + page.click("button.icon-menu") + page.locator("a").get_by_text("Invite").click() + expect(page.get_by_text("Baby McBaby")).not_to_be_visible() -def test_reschedule_participant(sb, sample_experiment, sample_participant, sample_leader, as_leader): +def test_reschedule_participant(page, sample_experiment, sample_participant, sample_leader, as_leader): """it should be possible to make a new appointment with the same participant, if a previous appointment was canceled""" sample_experiment.leaders.add(as_leader) @@ -158,17 +159,17 @@ def test_reschedule_participant(sb, sample_experiment, sample_participant, sampl ) # baby mcbaby shouldn't be available anymore - sb.click("a:contains(Experiments)") - sb.click("a:contains(Overview)") - sb.click("button.icon-menu") - sb.click("a:contains(Invite)") - sb.assert_text_not_visible("Baby McBaby") + page.get_by_role("button", name="Experiments").click() + page.get_by_role("link", name="Overview").click() + page.click("button.icon-menu") + page.locator("a").get_by_text("Invite").click() + expect(page.get_by_text("Baby McBaby")).not_to_be_visible() appointment.cancel() # baby mcbaby should be available again - sb.click("a:contains(Experiments)") - sb.click("a:contains(Overview)") - sb.click("button.icon-menu") - sb.click("a:contains(Invite)") - sb.assert_text_visible("Baby McBaby") + page.get_by_role("button", name="Experiments").click() + page.get_by_role("link", name="Overview").click() + page.click("button.icon-menu") + page.locator("a").get_by_text("Invite").click() + expect(page.get_by_text("Baby McBaby")).to_be_visible() diff --git a/lab/integration_tests/test_participants.py b/lab/integration_tests/test_participants.py index 96534d70..45d64ab5 100644 --- a/lab/integration_tests/test_participants.py +++ b/lab/integration_tests/test_participants.py @@ -1,12 +1,14 @@ -def test_participant_add_comment(sb, live_server, as_leader, sample_experiment, sample_participant): +from playwright.sync_api import expect + + +def test_participant_add_comment(page, live_server, as_leader, sample_experiment, sample_participant): sample_experiment.leaders.add(as_leader) - sb.open(live_server.url + "/participants") - sb.click(f"a:contains('{sample_participant.name}')") - sb.click("a:contains('new comment')") - sb.click("textarea") - sb.type("textarea", "hello this is a comment") - sb.click("button:contains(Send)") + page.goto(live_server.url + "/participants") + page.locator("a").get_by_text(sample_participant.name).click() + page.locator("a").get_by_text("new comment").click() + page.fill("textarea", "hello this is a comment") + page.get_by_role("button", name="Send").click() - sb.assert_text_not_visible("button:contains(Send)") - sb.assert_text_visible("hello this is a comment") - sb.assert_text(as_leader.name, "#participant-comments") \ No newline at end of file + expect(page.get_by_role("button", name="Send")).not_to_be_visible() + expect(page.get_by_text("hello this is a comment")).to_be_visible() + expect(page.locator("#participant-comments")).to_contain_text(as_leader.name) diff --git a/lab/integration_tests/test_signups.py b/lab/integration_tests/test_signups.py index 24951827..f70e5f62 100644 --- a/lab/integration_tests/test_signups.py +++ b/lab/integration_tests/test_signups.py @@ -2,6 +2,7 @@ import pytest from django.utils import timezone +from playwright.sync_api import expect from participants.models import Participant from signups.models import Signup @@ -29,38 +30,38 @@ def sample_signup(db): ) -def test_signup_approve(sb, sample_signup, live_server, as_admin): - sb.click("a:contains(Participants)") - sb.click("a:contains(Signups)") +def test_signup_approve(page, sample_signup, live_server, as_admin): + page.get_by_role("button", name="Participants").click() + page.locator("a").get_by_text("Signups").click() # check that the signup is visible somewhere within a table tag - sb.assert_text(sample_signup.name, "table") + expect(page.locator("table").get_by_text(sample_signup.name)).to_be_visible() - sb.click("tr:contains(details) a") - sb.click("button:contains(Approve)") + page.locator("a").get_by_text("details").click() + page.locator("button").get_by_text("Approve").click() # check that the signup is no longer visible - sb.click("a:contains(Participants)") - sb.click("a:contains(Signups)") - sb.assert_text_not_visible(sample_signup.name, "table") + page.get_by_role("button", name="Participants").click() + page.locator("a").get_by_text("Signups").click() + expect(page.locator("table").get_by_text(sample_signup.name)).not_to_be_visible() # check that a new participant is visible in the system - sb.open(live_server.url + "/participants/") - sb.assert_text(sample_signup.name, "table") + page.goto(live_server.url + "/participants/") + expect(page.locator("table").get_by_text(sample_signup.name)).to_be_visible() -def test_signup_reject(sb, sample_signup, live_server, as_admin): - sb.click("a:contains(Participants)") - sb.click("a:contains(Signups)") +def test_signup_reject(page, sample_signup, live_server, as_admin): + page.get_by_role("button", name="Participants").click() + page.locator("a").get_by_text("Signups").click() - sb.click("tr:contains(details) a") - sb.click("button:contains(Reject)") + page.locator("a").get_by_text("details").click() + page.locator("button").get_by_text("Reject").click() # check that the signup is no longer visible - sb.click("a:contains(Participants)") - sb.click("a:contains(Signups)") - sb.assert_text_not_visible(sample_signup.name, "table") + page.get_by_role("button", name="Participants").click() + page.locator("a").get_by_text("Signups").click() + expect(page.locator("table").get_by_text(sample_signup.name)).not_to_be_visible() # check that no new participant is visible in the system - sb.open(live_server.url + "/participants/") - sb.assert_text_not_visible(sample_signup.name, "table") + page.goto(live_server.url + "/participants/") + expect(page.locator("table").get_by_text(sample_signup.name)).not_to_be_visible() diff --git a/lab/requirements-dev.txt b/lab/requirements-dev.txt index 2c950275..983c6887 100644 --- a/lab/requirements-dev.txt +++ b/lab/requirements-dev.txt @@ -1,10 +1,13 @@ flake8 black isort -seleniumbase==4.15.5 mypy django-stubs>=1.13.0 djangorestframework-stubs jedi autopep8 faker + +playwright +pytest-playwright +pytest-xdist diff --git a/lab/requirements.in b/lab/requirements.in index 99d59d15..4fc1359c 100644 --- a/lab/requirements.in +++ b/lab/requirements.in @@ -18,4 +18,4 @@ pytest-django -e git+https://github.com/UiL-OTS-labs/django-vue3-tag@main#egg=django-vue3-tag matplotlib freezegun -django_json_widget +django_json_widget \ No newline at end of file diff --git a/parent/requirements-dev.txt b/parent/requirements-dev.txt index a1de3069..5ad2aabf 100644 --- a/parent/requirements-dev.txt +++ b/parent/requirements-dev.txt @@ -1,2 +1,2 @@ -seleniumbase +playwright pytest-django