From 38d450a91610b16a4c64d5371b64a95b3b741c45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sascha=20I=C3=9Fbr=C3=BCcker?= Date: Thu, 1 Jan 2026 01:46:31 +0100 Subject: [PATCH] Run tests in CI in parallel (#1254) * Run tests in CI in parallel * make tests automatically open/close playwright * fix parallel tests and screenshots * fix capturing screenshots for non-failing tests * cleanup * cleanup * format * log js errors * provide screenshots as artifacts * remove old scripts --- .github/workflows/main.yaml | 10 +- .gitignore | 1 + .../e2e_test_a11y_navigation_focus.py | 96 ++--- .../e2e_test_bookmark_details_modal.py | 206 +++++---- bookmarks/tests_e2e/e2e_test_bookmark_item.py | 19 +- .../e2e_test_bookmark_page_bulk_edit.py | 268 ++++++------ .../e2e_test_bookmark_page_partial_updates.py | 290 ++++++------- .../tests_e2e/e2e_test_bundle_preview.py | 53 ++- .../tests_e2e/e2e_test_collapse_side_panel.py | 32 +- bookmarks/tests_e2e/e2e_test_dropdown.py | 142 +++---- .../tests_e2e/e2e_test_edit_bookmark_form.py | 46 +- bookmarks/tests_e2e/e2e_test_filter_drawer.py | 82 ++-- .../tests_e2e/e2e_test_global_shortcuts.py | 30 +- .../tests_e2e/e2e_test_new_bookmark_form.py | 397 +++++++++--------- .../tests_e2e/e2e_test_settings_general.py | 122 +++--- .../e2e_test_settings_integrations.py | 82 ++-- .../tests_e2e/e2e_test_tag_management.py | 244 ++++++----- bookmarks/tests_e2e/helpers.py | 68 ++- scripts/setup-oicd.sh | 8 - scripts/test-e2e.sh | 12 - scripts/test-postgres.sh | 3 +- scripts/test-unit.sh | 1 - scripts/test.sh | 2 - 23 files changed, 1073 insertions(+), 1141 deletions(-) delete mode 100644 scripts/setup-oicd.sh delete mode 100755 scripts/test-e2e.sh delete mode 100755 scripts/test-unit.sh delete mode 100755 scripts/test.sh diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index b20f4ab..4e8e449 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -30,7 +30,7 @@ jobs: uv sync mkdir data - name: Run tests - run: uv run manage.py test bookmarks.tests + run: uv run pytest -n auto e2e_tests: name: E2E Tests runs-on: ubuntu-latest @@ -59,4 +59,10 @@ jobs: npm run build uv run manage.py collectstatic - name: Run tests - run: uv run manage.py test bookmarks.tests_e2e --pattern="e2e_test_*.py" + run: uv run pytest bookmarks/tests_e2e -n auto -o "python_files=e2e_test_*.py" + - name: Upload screenshots + if: failure() + uses: actions/upload-artifact@v4 + with: + name: e2e-screenshots + path: test-results/screenshots diff --git a/.gitignore b/.gitignore index 6467f6a..f7537a5 100644 --- a/.gitignore +++ b/.gitignore @@ -60,6 +60,7 @@ coverage.xml *.cover .hypothesis/ .pytest_cache/ +test-results/ # Translations *.mo diff --git a/bookmarks/tests_e2e/e2e_test_a11y_navigation_focus.py b/bookmarks/tests_e2e/e2e_test_a11y_navigation_focus.py index 874020d..461c06e 100644 --- a/bookmarks/tests_e2e/e2e_test_a11y_navigation_focus.py +++ b/bookmarks/tests_e2e/e2e_test_a11y_navigation_focus.py @@ -1,73 +1,69 @@ from django.urls import reverse -from playwright.sync_api import sync_playwright, expect +from playwright.sync_api import expect from bookmarks.tests_e2e.helpers import LinkdingE2ETestCase class A11yNavigationFocusTest(LinkdingE2ETestCase): def test_initial_page_load_focus(self): - with sync_playwright() as p: - # First page load should keep focus on the body - page = self.open(reverse("linkding:bookmarks.index"), p) - focused_tag = page.evaluate("document.activeElement?.tagName") - self.assertEqual("BODY", focused_tag) + # First page load should keep focus on the body + page = self.open(reverse("linkding:bookmarks.index")) + focused_tag = page.evaluate("document.activeElement?.tagName") + self.assertEqual("BODY", focused_tag) - page.goto(self.live_server_url + reverse("linkding:bookmarks.archived")) - focused_tag = page.evaluate("document.activeElement?.tagName") - self.assertEqual("BODY", focused_tag) + page.goto(self.live_server_url + reverse("linkding:bookmarks.archived")) + focused_tag = page.evaluate("document.activeElement?.tagName") + self.assertEqual("BODY", focused_tag) - page.goto(self.live_server_url + reverse("linkding:settings.general")) - focused_tag = page.evaluate("document.activeElement?.tagName") - self.assertEqual("BODY", focused_tag) + page.goto(self.live_server_url + reverse("linkding:settings.general")) + focused_tag = page.evaluate("document.activeElement?.tagName") + self.assertEqual("BODY", focused_tag) - # Bookmark form views should focus the URL input - page.goto(self.live_server_url + reverse("linkding:bookmarks.new")) - page.wait_for_timeout(timeout=1000) - focused_tag = page.evaluate( - "document.activeElement?.tagName + '|' + document.activeElement?.name" - ) - self.assertEqual("INPUT|url", focused_tag) + # Bookmark form views should focus the URL input + page.goto(self.live_server_url + reverse("linkding:bookmarks.new")) + page.wait_for_timeout(timeout=1000) + focused_tag = page.evaluate( + "document.activeElement?.tagName + '|' + document.activeElement?.name" + ) + self.assertEqual("INPUT|url", focused_tag) def test_page_navigation_focus(self): bookmark = self.setup_bookmark() - with sync_playwright() as p: - page = self.open(reverse("linkding:bookmarks.index"), p) + page = self.open(reverse("linkding:bookmarks.index")) - # Subsequent navigation should move focus to main content - self.reset_focus() - self.navigate_menu("Bookmarks", "Active") - focused = page.locator("main:focus") - expect(focused).to_be_visible() + # Subsequent navigation should move focus to main content + self.reset_focus() + self.navigate_menu("Bookmarks", "Active") + focused = page.locator("main:focus") + expect(focused).to_be_visible() - self.reset_focus() - self.navigate_menu("Bookmarks", "Archived") - focused = page.locator("main:focus") - expect(focused).to_be_visible() + self.reset_focus() + self.navigate_menu("Bookmarks", "Archived") + focused = page.locator("main:focus") + expect(focused).to_be_visible() - self.reset_focus() - self.navigate_menu("Settings", "General") - focused = page.locator("main:focus") - expect(focused).to_be_visible() + self.reset_focus() + self.navigate_menu("Settings", "General") + focused = page.locator("main:focus") + expect(focused).to_be_visible() - # Bookmark form views should focus the URL input - self.reset_focus() - self.navigate_menu("Add bookmark") - focused = page.locator("input[name='url']:focus") - expect(focused).to_be_visible() + # Bookmark form views should focus the URL input + self.reset_focus() + self.navigate_menu("Add bookmark") + focused = page.locator("input[name='url']:focus") + expect(focused).to_be_visible() - # Opening details modal should move focus to close button - self.navigate_menu("Bookmarks", "Active") - self.open_details_modal(bookmark) - focused = page.locator(".modal button.close:focus") - expect(focused).to_be_visible() + # Opening details modal should move focus to close button + self.navigate_menu("Bookmarks", "Active") + self.open_details_modal(bookmark) + focused = page.locator(".modal button.close:focus") + expect(focused).to_be_visible() - # Closing modal should move focus back to the bookmark item - page.keyboard.press("Escape") - focused = self.locate_bookmark(bookmark.title).locator( - "a.view-action:focus" - ) - expect(focused).to_be_visible() + # Closing modal should move focus back to the bookmark item + page.keyboard.press("Escape") + focused = self.locate_bookmark(bookmark.title).locator("a.view-action:focus") + expect(focused).to_be_visible() def reset_focus(self): self.page.evaluate("document.activeElement.blur()") diff --git a/bookmarks/tests_e2e/e2e_test_bookmark_details_modal.py b/bookmarks/tests_e2e/e2e_test_bookmark_details_modal.py index 235f7c0..13cb8b0 100644 --- a/bookmarks/tests_e2e/e2e_test_bookmark_details_modal.py +++ b/bookmarks/tests_e2e/e2e_test_bookmark_details_modal.py @@ -1,6 +1,6 @@ from django.test import override_settings from django.urls import reverse -from playwright.sync_api import sync_playwright, expect +from playwright.sync_api import expect from bookmarks.tests_e2e.helpers import LinkdingE2ETestCase from bookmarks.models import Bookmark @@ -10,78 +10,74 @@ class BookmarkDetailsModalE2ETestCase(LinkdingE2ETestCase): def test_show_details(self): bookmark = self.setup_bookmark() - with sync_playwright() as p: - self.open(reverse("linkding:bookmarks.index"), p) + self.open(reverse("linkding:bookmarks.index")) - details_modal = self.open_details_modal(bookmark) - title = details_modal.locator("h2") - expect(title).to_have_text(bookmark.title) + details_modal = self.open_details_modal(bookmark) + title = details_modal.locator("h2") + expect(title).to_have_text(bookmark.title) def test_close_details(self): bookmark = self.setup_bookmark() - with sync_playwright() as p: - self.open(reverse("linkding:bookmarks.index"), p) + self.open(reverse("linkding:bookmarks.index")) - # close with close button - details_modal = self.open_details_modal(bookmark) - details_modal.locator("button.close").click() - expect(details_modal).to_be_hidden() + # close with close button + details_modal = self.open_details_modal(bookmark) + details_modal.locator("button.close").click() + expect(details_modal).to_be_hidden() - # close with backdrop - details_modal = self.open_details_modal(bookmark) - overlay = details_modal.locator(".modal-overlay") - overlay.click(position={"x": 0, "y": 0}) - expect(details_modal).to_be_hidden() + # close with backdrop + details_modal = self.open_details_modal(bookmark) + overlay = details_modal.locator(".modal-overlay") + overlay.click(position={"x": 0, "y": 0}) + expect(details_modal).to_be_hidden() - # close with escape - details_modal = self.open_details_modal(bookmark) - self.page.keyboard.press("Escape") - expect(details_modal).to_be_hidden() + # close with escape + details_modal = self.open_details_modal(bookmark) + self.page.keyboard.press("Escape") + expect(details_modal).to_be_hidden() def test_toggle_archived(self): bookmark = self.setup_bookmark() - with sync_playwright() as p: - # archive - url = reverse("linkding:bookmarks.index") - self.open(url, p) + # archive + url = reverse("linkding:bookmarks.index") + self.open(url) - details_modal = self.open_details_modal(bookmark) - details_modal.get_by_text("Archived", exact=False).click() - expect(self.locate_bookmark(bookmark.title)).not_to_be_visible() - self.assertReloads(0) + details_modal = self.open_details_modal(bookmark) + details_modal.get_by_text("Archived", exact=False).click() + expect(self.locate_bookmark(bookmark.title)).not_to_be_visible() + self.assertReloads(0) - # unarchive - url = reverse("linkding:bookmarks.archived") - self.page.goto(self.live_server_url + url) - self.resetReloads() + # unarchive + url = reverse("linkding:bookmarks.archived") + self.page.goto(self.live_server_url + url) + self.resetReloads() - details_modal = self.open_details_modal(bookmark) - details_modal.get_by_text("Archived", exact=False).click() - expect(self.locate_bookmark(bookmark.title)).not_to_be_visible() - self.assertReloads(0) + details_modal = self.open_details_modal(bookmark) + details_modal.get_by_text("Archived", exact=False).click() + expect(self.locate_bookmark(bookmark.title)).not_to_be_visible() + self.assertReloads(0) def test_toggle_unread(self): bookmark = self.setup_bookmark() - with sync_playwright() as p: - # mark as unread - url = reverse("linkding:bookmarks.index") - self.open(url, p) + # mark as unread + url = reverse("linkding:bookmarks.index") + self.open(url) - details_modal = self.open_details_modal(bookmark) + details_modal = self.open_details_modal(bookmark) - details_modal.get_by_text("Unread").click() - bookmark_item = self.locate_bookmark(bookmark.title) - expect(bookmark_item.get_by_text("Unread")).to_be_visible() - self.assertReloads(0) + details_modal.get_by_text("Unread").click() + bookmark_item = self.locate_bookmark(bookmark.title) + expect(bookmark_item.get_by_text("Unread")).to_be_visible() + self.assertReloads(0) - # mark as read - details_modal.get_by_text("Unread").click() - bookmark_item = self.locate_bookmark(bookmark.title) - expect(bookmark_item.get_by_text("Unread")).not_to_be_visible() - self.assertReloads(0) + # mark as read + details_modal.get_by_text("Unread").click() + bookmark_item = self.locate_bookmark(bookmark.title) + expect(bookmark_item.get_by_text("Unread")).not_to_be_visible() + self.assertReloads(0) def test_toggle_shared(self): profile = self.get_or_create_test_user().profile @@ -90,61 +86,58 @@ class BookmarkDetailsModalE2ETestCase(LinkdingE2ETestCase): bookmark = self.setup_bookmark() - with sync_playwright() as p: - # share bookmark - url = reverse("linkding:bookmarks.index") - self.open(url, p) + # share bookmark + url = reverse("linkding:bookmarks.index") + self.open(url) - details_modal = self.open_details_modal(bookmark) + details_modal = self.open_details_modal(bookmark) - details_modal.get_by_text("Shared").click() - bookmark_item = self.locate_bookmark(bookmark.title) - expect(bookmark_item.get_by_text("Shared")).to_be_visible() - self.assertReloads(0) + details_modal.get_by_text("Shared").click() + bookmark_item = self.locate_bookmark(bookmark.title) + expect(bookmark_item.get_by_text("Shared")).to_be_visible() + self.assertReloads(0) - # unshare bookmark - details_modal.get_by_text("Shared").click() - bookmark_item = self.locate_bookmark(bookmark.title) - expect(bookmark_item.get_by_text("Shared")).not_to_be_visible() - self.assertReloads(0) + # unshare bookmark + details_modal.get_by_text("Shared").click() + bookmark_item = self.locate_bookmark(bookmark.title) + expect(bookmark_item.get_by_text("Shared")).not_to_be_visible() + self.assertReloads(0) def test_edit_return_url(self): bookmark = self.setup_bookmark() - with sync_playwright() as p: - url = reverse("linkding:bookmarks.index") + f"?q={bookmark.title}" - self.open(url, p) + url = reverse("linkding:bookmarks.index") + f"?q={bookmark.title}" + self.open(url) - details_modal = self.open_details_modal(bookmark) + details_modal = self.open_details_modal(bookmark) - # Navigate to edit page - with self.page.expect_navigation(): - details_modal.get_by_text("Edit").click() + # Navigate to edit page + with self.page.expect_navigation(): + details_modal.get_by_text("Edit").click() - # Cancel edit, verify return to details url - details_url = url + f"&details={bookmark.id}" - with self.page.expect_navigation(url=self.live_server_url + details_url): - self.page.get_by_text("Cancel").click() + # Cancel edit, verify return to details url + details_url = url + f"&details={bookmark.id}" + with self.page.expect_navigation(url=self.live_server_url + details_url): + self.page.get_by_text("Cancel").click() def test_delete(self): bookmark = self.setup_bookmark() - with sync_playwright() as p: - url = reverse("linkding:bookmarks.index") + f"?q={bookmark.title}" - self.open(url, p) + url = reverse("linkding:bookmarks.index") + f"?q={bookmark.title}" + self.open(url) - details_modal = self.open_details_modal(bookmark) + details_modal = self.open_details_modal(bookmark) - # Wait for confirm button to be initialized - self.page.wait_for_timeout(1000) + # Wait for confirm button to be initialized + self.page.wait_for_timeout(1000) - # Delete bookmark, verify return url - with self.page.expect_navigation(url=self.live_server_url + url): - details_modal.get_by_text("Delete").click() - self.locate_confirm_dialog().get_by_text("Confirm").click() + # Delete bookmark, verify return url + with self.page.expect_navigation(url=self.live_server_url + url): + details_modal.get_by_text("Delete").click() + self.locate_confirm_dialog().get_by_text("Confirm").click() - # verify bookmark is deleted - expect(self.locate_bookmark(bookmark.title)).not_to_be_visible() + # verify bookmark is deleted + expect(self.locate_bookmark(bookmark.title)).not_to_be_visible() self.assertEqual(Bookmark.objects.count(), 0) @@ -152,28 +145,27 @@ class BookmarkDetailsModalE2ETestCase(LinkdingE2ETestCase): def test_create_snapshot_remove_snapshot(self): bookmark = self.setup_bookmark() - with sync_playwright() as p: - url = reverse("linkding:bookmarks.index") + f"?q={bookmark.title}" - self.open(url, p) + url = reverse("linkding:bookmarks.index") + f"?q={bookmark.title}" + self.open(url) - details_modal = self.open_details_modal(bookmark) - asset_list = details_modal.locator(".assets") + details_modal = self.open_details_modal(bookmark) + asset_list = details_modal.locator(".assets") - # No snapshots initially - snapshot = asset_list.get_by_text("HTML snapshot from", exact=False) - expect(snapshot).not_to_be_visible() + # No snapshots initially + snapshot = asset_list.get_by_text("HTML snapshot from", exact=False) + expect(snapshot).not_to_be_visible() - # Create snapshot - details_modal.get_by_text("Create HTML snapshot", exact=False).click() - self.assertReloads(0) + # Create snapshot + details_modal.get_by_text("Create HTML snapshot", exact=False).click() + self.assertReloads(0) - # Has new snapshots - expect(snapshot).to_be_visible() + # Has new snapshots + expect(snapshot).to_be_visible() - # Remove snapshot - asset_list.get_by_text("Remove", exact=False).click() - self.locate_confirm_dialog().get_by_text("Confirm", exact=False).click() + # Remove snapshot + asset_list.get_by_text("Remove", exact=False).click() + self.locate_confirm_dialog().get_by_text("Confirm", exact=False).click() - # Snapshot is removed - expect(snapshot).not_to_be_visible() - self.assertReloads(0) + # Snapshot is removed + expect(snapshot).not_to_be_visible() + self.assertReloads(0) diff --git a/bookmarks/tests_e2e/e2e_test_bookmark_item.py b/bookmarks/tests_e2e/e2e_test_bookmark_item.py index e16aa22..4c428eb 100644 --- a/bookmarks/tests_e2e/e2e_test_bookmark_item.py +++ b/bookmarks/tests_e2e/e2e_test_bookmark_item.py @@ -1,7 +1,7 @@ from unittest import skip from django.urls import reverse -from playwright.sync_api import sync_playwright, expect +from playwright.sync_api import expect from bookmarks.tests_e2e.helpers import LinkdingE2ETestCase @@ -11,15 +11,14 @@ class BookmarkItemE2ETestCase(LinkdingE2ETestCase): def test_toggle_notes_should_show_hide_notes(self): bookmark = self.setup_bookmark(notes="Test notes") - with sync_playwright() as p: - page = self.open(reverse("linkding:bookmarks.index"), p) + page = self.open(reverse("linkding:bookmarks.index")) - notes = self.locate_bookmark(bookmark.title).locator(".notes") - expect(notes).to_be_hidden() + notes = self.locate_bookmark(bookmark.title).locator(".notes") + expect(notes).to_be_hidden() - toggle_notes = page.locator("li button.toggle-notes") - toggle_notes.click() - expect(notes).to_be_visible() + toggle_notes = page.locator("li button.toggle-notes") + toggle_notes.click() + expect(notes).to_be_visible() - toggle_notes.click() - expect(notes).to_be_hidden() + toggle_notes.click() + expect(notes).to_be_hidden() diff --git a/bookmarks/tests_e2e/e2e_test_bookmark_page_bulk_edit.py b/bookmarks/tests_e2e/e2e_test_bookmark_page_bulk_edit.py index ab1c3f9..022cba1 100644 --- a/bookmarks/tests_e2e/e2e_test_bookmark_page_bulk_edit.py +++ b/bookmarks/tests_e2e/e2e_test_bookmark_page_bulk_edit.py @@ -1,5 +1,5 @@ from django.urls import reverse -from playwright.sync_api import sync_playwright, expect +from playwright.sync_api import expect from bookmarks.tests_e2e.helpers import LinkdingE2ETestCase from bookmarks.models import Bookmark @@ -36,19 +36,18 @@ class BookmarkPageBulkEditE2ETestCase(LinkdingE2ETestCase): def test_active_bookmarks_bulk_select_across(self): self.setup_test_data() - with sync_playwright() as p: - self.open(reverse("linkding:bookmarks.index"), p) + self.open(reverse("linkding:bookmarks.index")) - bookmark_list = self.locate_bookmark_list().element_handle() - self.locate_bulk_edit_toggle().click() - self.locate_bulk_edit_select_all().click() - self.locate_bulk_edit_select_across().click() + bookmark_list = self.locate_bookmark_list().element_handle() + self.locate_bulk_edit_toggle().click() + self.locate_bulk_edit_select_all().click() + self.locate_bulk_edit_select_across().click() - self.select_bulk_action("Delete") - self.locate_bulk_edit_bar().get_by_text("Execute").click() - self.locate_confirm_dialog().get_by_text("Confirm").click() - # Wait until bookmark list is updated (old reference becomes invisible) - bookmark_list.wait_for_element_state("hidden", timeout=1000) + self.select_bulk_action("Delete") + self.locate_bulk_edit_bar().get_by_text("Execute").click() + self.locate_confirm_dialog().get_by_text("Confirm").click() + # Wait until bookmark list is updated (old reference becomes invisible) + bookmark_list.wait_for_element_state("hidden", timeout=1000) self.assertEqual( 0, @@ -74,19 +73,18 @@ class BookmarkPageBulkEditE2ETestCase(LinkdingE2ETestCase): def test_archived_bookmarks_bulk_select_across(self): self.setup_test_data() - with sync_playwright() as p: - self.open(reverse("linkding:bookmarks.archived"), p) + self.open(reverse("linkding:bookmarks.archived")) - bookmark_list = self.locate_bookmark_list().element_handle() - self.locate_bulk_edit_toggle().click() - self.locate_bulk_edit_select_all().click() - self.locate_bulk_edit_select_across().click() + bookmark_list = self.locate_bookmark_list().element_handle() + self.locate_bulk_edit_toggle().click() + self.locate_bulk_edit_select_all().click() + self.locate_bulk_edit_select_across().click() - self.select_bulk_action("Delete") - self.locate_bulk_edit_bar().get_by_text("Execute").click() - self.locate_confirm_dialog().get_by_text("Confirm").click() - # Wait until bookmark list is updated (old reference becomes invisible) - bookmark_list.wait_for_element_state("hidden", timeout=1000) + self.select_bulk_action("Delete") + self.locate_bulk_edit_bar().get_by_text("Execute").click() + self.locate_confirm_dialog().get_by_text("Confirm").click() + # Wait until bookmark list is updated (old reference becomes invisible) + bookmark_list.wait_for_element_state("hidden", timeout=1000) self.assertEqual( 50, @@ -112,19 +110,18 @@ class BookmarkPageBulkEditE2ETestCase(LinkdingE2ETestCase): def test_active_bookmarks_bulk_select_across_respects_query(self): self.setup_test_data() - with sync_playwright() as p: - self.open(reverse("linkding:bookmarks.index") + "?q=foo", p) + self.open(reverse("linkding:bookmarks.index") + "?q=foo") - bookmark_list = self.locate_bookmark_list().element_handle() - self.locate_bulk_edit_toggle().click() - self.locate_bulk_edit_select_all().click() - self.locate_bulk_edit_select_across().click() + bookmark_list = self.locate_bookmark_list().element_handle() + self.locate_bulk_edit_toggle().click() + self.locate_bulk_edit_select_all().click() + self.locate_bulk_edit_select_across().click() - self.select_bulk_action("Delete") - self.locate_bulk_edit_bar().get_by_text("Execute").click() - self.locate_confirm_dialog().get_by_text("Confirm").click() - # Wait until bookmark list is updated (old reference becomes invisible) - bookmark_list.wait_for_element_state("hidden", timeout=1000) + self.select_bulk_action("Delete") + self.locate_bulk_edit_bar().get_by_text("Execute").click() + self.locate_confirm_dialog().get_by_text("Confirm").click() + # Wait until bookmark list is updated (old reference becomes invisible) + bookmark_list.wait_for_element_state("hidden", timeout=1000) self.assertEqual( 50, @@ -150,19 +147,18 @@ class BookmarkPageBulkEditE2ETestCase(LinkdingE2ETestCase): def test_archived_bookmarks_bulk_select_across_respects_query(self): self.setup_test_data() - with sync_playwright() as p: - self.open(reverse("linkding:bookmarks.archived") + "?q=foo", p) + self.open(reverse("linkding:bookmarks.archived") + "?q=foo") - bookmark_list = self.locate_bookmark_list().element_handle() - self.locate_bulk_edit_toggle().click() - self.locate_bulk_edit_select_all().click() - self.locate_bulk_edit_select_across().click() + bookmark_list = self.locate_bookmark_list().element_handle() + self.locate_bulk_edit_toggle().click() + self.locate_bulk_edit_select_all().click() + self.locate_bulk_edit_select_across().click() - self.select_bulk_action("Delete") - self.locate_bulk_edit_bar().get_by_text("Execute").click() - self.locate_confirm_dialog().get_by_text("Confirm").click() - # Wait until bookmark list is updated (old reference becomes invisible) - bookmark_list.wait_for_element_state("hidden", timeout=1000) + self.select_bulk_action("Delete") + self.locate_bulk_edit_bar().get_by_text("Execute").click() + self.locate_confirm_dialog().get_by_text("Confirm").click() + # Wait until bookmark list is updated (old reference becomes invisible) + bookmark_list.wait_for_element_state("hidden", timeout=1000) self.assertEqual( 50, @@ -188,148 +184,138 @@ class BookmarkPageBulkEditE2ETestCase(LinkdingE2ETestCase): def test_select_all_toggles_all_checkboxes(self): self.setup_numbered_bookmarks(5) - with sync_playwright() as p: - url = reverse("linkding:bookmarks.index") - page = self.open(url, p) + url = reverse("linkding:bookmarks.index") + page = self.open(url) - self.locate_bulk_edit_toggle().click() + self.locate_bulk_edit_toggle().click() - checkboxes = page.locator("label.bulk-edit-checkbox input") - self.assertEqual(6, checkboxes.count()) - for i in range(checkboxes.count()): - expect(checkboxes.nth(i)).not_to_be_checked() + checkboxes = page.locator("label.bulk-edit-checkbox input") + self.assertEqual(6, checkboxes.count()) + for i in range(checkboxes.count()): + expect(checkboxes.nth(i)).not_to_be_checked() - self.locate_bulk_edit_select_all().click() + self.locate_bulk_edit_select_all().click() - for i in range(checkboxes.count()): - expect(checkboxes.nth(i)).to_be_checked() + for i in range(checkboxes.count()): + expect(checkboxes.nth(i)).to_be_checked() - self.locate_bulk_edit_select_all().click() + self.locate_bulk_edit_select_all().click() - for i in range(checkboxes.count()): - expect(checkboxes.nth(i)).not_to_be_checked() + for i in range(checkboxes.count()): + expect(checkboxes.nth(i)).not_to_be_checked() def test_select_all_shows_select_across(self): self.setup_numbered_bookmarks(5) - with sync_playwright() as p: - url = reverse("linkding:bookmarks.index") - self.open(url, p) + url = reverse("linkding:bookmarks.index") + self.open(url) - self.locate_bulk_edit_toggle().click() + self.locate_bulk_edit_toggle().click() - expect(self.locate_bulk_edit_select_across()).not_to_be_visible() + expect(self.locate_bulk_edit_select_across()).not_to_be_visible() - self.locate_bulk_edit_select_all().click() - expect(self.locate_bulk_edit_select_across()).to_be_visible() + self.locate_bulk_edit_select_all().click() + expect(self.locate_bulk_edit_select_across()).to_be_visible() - self.locate_bulk_edit_select_all().click() - expect(self.locate_bulk_edit_select_across()).not_to_be_visible() + self.locate_bulk_edit_select_all().click() + expect(self.locate_bulk_edit_select_across()).not_to_be_visible() def test_select_across_is_unchecked_when_toggling_all(self): self.setup_numbered_bookmarks(5) - with sync_playwright() as p: - url = reverse("linkding:bookmarks.index") - self.open(url, p) + url = reverse("linkding:bookmarks.index") + self.open(url) - self.locate_bulk_edit_toggle().click() + self.locate_bulk_edit_toggle().click() - # Show select across, check it - self.locate_bulk_edit_select_all().click() - self.locate_bulk_edit_select_across().click() - expect(self.locate_bulk_edit_select_across()).to_be_checked() + # Show select across, check it + self.locate_bulk_edit_select_all().click() + self.locate_bulk_edit_select_across().click() + expect(self.locate_bulk_edit_select_across()).to_be_checked() - # Hide select across by toggling select all - self.locate_bulk_edit_select_all().click() - expect(self.locate_bulk_edit_select_across()).not_to_be_visible() + # Hide select across by toggling select all + self.locate_bulk_edit_select_all().click() + expect(self.locate_bulk_edit_select_across()).not_to_be_visible() - # Show select across again, verify it is unchecked - self.locate_bulk_edit_select_all().click() - expect(self.locate_bulk_edit_select_across()).not_to_be_checked() + # Show select across again, verify it is unchecked + self.locate_bulk_edit_select_all().click() + expect(self.locate_bulk_edit_select_across()).not_to_be_checked() def test_select_across_is_unchecked_when_toggling_bookmark(self): self.setup_numbered_bookmarks(5) - with sync_playwright() as p: - url = reverse("linkding:bookmarks.index") - self.open(url, p) + url = reverse("linkding:bookmarks.index") + self.open(url) - self.locate_bulk_edit_toggle().click() + self.locate_bulk_edit_toggle().click() - # Show select across, check it - self.locate_bulk_edit_select_all().click() - self.locate_bulk_edit_select_across().click() - expect(self.locate_bulk_edit_select_across()).to_be_checked() + # Show select across, check it + self.locate_bulk_edit_select_all().click() + self.locate_bulk_edit_select_across().click() + expect(self.locate_bulk_edit_select_across()).to_be_checked() - # Hide select across by toggling a single bookmark - self.locate_bookmark("Bookmark 1").locator( - "label.bulk-edit-checkbox" - ).click() - expect(self.locate_bulk_edit_select_across()).not_to_be_visible() + # Hide select across by toggling a single bookmark + self.locate_bookmark("Bookmark 1").locator("label.bulk-edit-checkbox").click() + expect(self.locate_bulk_edit_select_across()).not_to_be_visible() - # Show select across again, verify it is unchecked - self.locate_bookmark("Bookmark 1").locator( - "label.bulk-edit-checkbox" - ).click() - expect(self.locate_bulk_edit_select_across()).not_to_be_checked() + # Show select across again, verify it is unchecked + self.locate_bookmark("Bookmark 1").locator("label.bulk-edit-checkbox").click() + expect(self.locate_bulk_edit_select_across()).not_to_be_checked() def test_execute_resets_all_checkboxes(self): self.setup_numbered_bookmarks(100) - with sync_playwright() as p: - url = reverse("linkding:bookmarks.index") - page = self.open(url, p) + url = reverse("linkding:bookmarks.index") + page = self.open(url) - bookmark_list = self.locate_bookmark_list().element_handle() + bookmark_list = self.locate_bookmark_list().element_handle() - # Select all bookmarks, enable select across - self.locate_bulk_edit_toggle().click() - self.locate_bulk_edit_select_all().click() - self.locate_bulk_edit_select_across().click() + # Select all bookmarks, enable select across + self.locate_bulk_edit_toggle().click() + self.locate_bulk_edit_select_all().click() + self.locate_bulk_edit_select_across().click() - # Execute bulk action - self.select_bulk_action("Mark as unread") - self.locate_bulk_edit_bar().get_by_text("Execute").click() - self.locate_confirm_dialog().get_by_text("Confirm").click() + # Execute bulk action + self.select_bulk_action("Mark as unread") + self.locate_bulk_edit_bar().get_by_text("Execute").click() + self.locate_confirm_dialog().get_by_text("Confirm").click() - # Wait until bookmark list is updated (old reference becomes invisible) - bookmark_list.wait_for_element_state("hidden", timeout=1000) + # Wait until bookmark list is updated (old reference becomes invisible) + bookmark_list.wait_for_element_state("hidden", timeout=1000) - # Verify bulk edit checkboxes are reset - checkboxes = page.locator("label.bulk-edit-checkbox input") - self.assertEqual(31, checkboxes.count()) - for i in range(checkboxes.count()): - expect(checkboxes.nth(i)).not_to_be_checked() + # Verify bulk edit checkboxes are reset + checkboxes = page.locator("label.bulk-edit-checkbox input") + self.assertEqual(31, checkboxes.count()) + for i in range(checkboxes.count()): + expect(checkboxes.nth(i)).not_to_be_checked() - # Toggle select all and verify select across is reset - self.locate_bulk_edit_select_all().click() - expect(self.locate_bulk_edit_select_across()).not_to_be_checked() + # Toggle select all and verify select across is reset + self.locate_bulk_edit_select_all().click() + expect(self.locate_bulk_edit_select_across()).not_to_be_checked() def test_update_select_across_bookmark_count(self): self.setup_numbered_bookmarks(100) - with sync_playwright() as p: - url = reverse("linkding:bookmarks.index") - self.open(url, p) + url = reverse("linkding:bookmarks.index") + self.open(url) - bookmark_list = self.locate_bookmark_list().element_handle() - self.locate_bulk_edit_toggle().click() - self.locate_bulk_edit_select_all().click() + bookmark_list = self.locate_bookmark_list().element_handle() + self.locate_bulk_edit_toggle().click() + self.locate_bulk_edit_select_all().click() - expect( - self.locate_bulk_edit_bar().get_by_text("All pages (100 bookmarks)") - ).to_be_visible() + expect( + self.locate_bulk_edit_bar().get_by_text("All pages (100 bookmarks)") + ).to_be_visible() - self.select_bulk_action("Delete") - self.locate_bulk_edit_bar().get_by_text("Execute").click() - self.locate_confirm_dialog().get_by_text("Confirm").click() - # Wait until bookmark list is updated (old reference becomes invisible) - bookmark_list.wait_for_element_state("hidden", timeout=1000) + self.select_bulk_action("Delete") + self.locate_bulk_edit_bar().get_by_text("Execute").click() + self.locate_confirm_dialog().get_by_text("Confirm").click() + # Wait until bookmark list is updated (old reference becomes invisible) + bookmark_list.wait_for_element_state("hidden", timeout=1000) - expect(self.locate_bulk_edit_select_all()).not_to_be_checked() - self.locate_bulk_edit_select_all().click() + expect(self.locate_bulk_edit_select_all()).not_to_be_checked() + self.locate_bulk_edit_select_all().click() - expect( - self.locate_bulk_edit_bar().get_by_text("All pages (70 bookmarks)") - ).to_be_visible() + expect( + self.locate_bulk_edit_bar().get_by_text("All pages (70 bookmarks)") + ).to_be_visible() diff --git a/bookmarks/tests_e2e/e2e_test_bookmark_page_partial_updates.py b/bookmarks/tests_e2e/e2e_test_bookmark_page_partial_updates.py index c1fbae3..15e0c9e 100644 --- a/bookmarks/tests_e2e/e2e_test_bookmark_page_partial_updates.py +++ b/bookmarks/tests_e2e/e2e_test_bookmark_page_partial_updates.py @@ -1,7 +1,7 @@ from typing import List from django.urls import reverse -from playwright.sync_api import sync_playwright, expect +from playwright.sync_api import expect from bookmarks.tests_e2e.helpers import LinkdingE2ETestCase @@ -43,91 +43,85 @@ class BookmarkPagePartialUpdatesE2ETestCase(LinkdingE2ETestCase): self.setup_numbered_bookmarks(5, prefix="foo") self.setup_numbered_bookmarks(5, prefix="bar") - with sync_playwright() as p: - url = reverse("linkding:bookmarks.index") + "?q=foo" - self.open(url, p) + url = reverse("linkding:bookmarks.index") + "?q=foo" + self.open(url) - self.assertVisibleBookmarks(["foo 1", "foo 2", "foo 3", "foo 4", "foo 5"]) + self.assertVisibleBookmarks(["foo 1", "foo 2", "foo 3", "foo 4", "foo 5"]) - self.locate_bookmark("foo 2").get_by_text("Archive").click() - self.assertVisibleBookmarks(["foo 1", "foo 3", "foo 4", "foo 5"]) + self.locate_bookmark("foo 2").get_by_text("Archive").click() + self.assertVisibleBookmarks(["foo 1", "foo 3", "foo 4", "foo 5"]) def test_partial_update_respects_sort(self): self.setup_numbered_bookmarks(5, prefix="foo") - with sync_playwright() as p: - url = reverse("linkding:bookmarks.index") + "?sort=title_asc" - page = self.open(url, p) + url = reverse("linkding:bookmarks.index") + "?sort=title_asc" + page = self.open(url) - first_item = page.locator("ul.bookmark-list > li").first - expect(first_item).to_contain_text("foo 1") + first_item = page.locator("ul.bookmark-list > li").first + expect(first_item).to_contain_text("foo 1") - first_item.get_by_text("Archive").click() + first_item.get_by_text("Archive").click() - first_item = page.locator("ul.bookmark-list > li").first - expect(first_item).to_contain_text("foo 2") + first_item = page.locator("ul.bookmark-list > li").first + expect(first_item).to_contain_text("foo 2") def test_partial_update_respects_page(self): # add a suffix, otherwise 'foo 1' also matches 'foo 10' self.setup_numbered_bookmarks(50, prefix="foo", suffix="-") - with sync_playwright() as p: - url = reverse("linkding:bookmarks.index") + "?q=foo&page=2" - self.open(url, p) + url = reverse("linkding:bookmarks.index") + "?q=foo&page=2" + self.open(url) - # with descending sort, page two has 'foo 1' to 'foo 20' - expected_titles = [f"foo {i}-" for i in range(1, 21)] - self.assertVisibleBookmarks(expected_titles) + # with descending sort, page two has 'foo 1' to 'foo 20' + expected_titles = [f"foo {i}-" for i in range(1, 21)] + self.assertVisibleBookmarks(expected_titles) - self.locate_bookmark("foo 20-").get_by_text("Archive").click() + self.locate_bookmark("foo 20-").get_by_text("Archive").click() - expected_titles = [f"foo {i}-" for i in range(1, 20)] - self.assertVisibleBookmarks(expected_titles) + expected_titles = [f"foo {i}-" for i in range(1, 20)] + self.assertVisibleBookmarks(expected_titles) def test_multiple_partial_updates(self): self.setup_numbered_bookmarks(5) - with sync_playwright() as p: - url = reverse("linkding:bookmarks.index") - self.open(url, p) + url = reverse("linkding:bookmarks.index") + self.open(url) - self.locate_bookmark("Bookmark 1").get_by_text("Archive").click() - self.assertVisibleBookmarks( - ["Bookmark 2", "Bookmark 3", "Bookmark 4", "Bookmark 5"] - ) + self.locate_bookmark("Bookmark 1").get_by_text("Archive").click() + self.assertVisibleBookmarks( + ["Bookmark 2", "Bookmark 3", "Bookmark 4", "Bookmark 5"] + ) - self.locate_bookmark("Bookmark 2").get_by_text("Archive").click() - self.assertVisibleBookmarks(["Bookmark 3", "Bookmark 4", "Bookmark 5"]) + self.locate_bookmark("Bookmark 2").get_by_text("Archive").click() + self.assertVisibleBookmarks(["Bookmark 3", "Bookmark 4", "Bookmark 5"]) - self.locate_bookmark("Bookmark 3").get_by_text("Archive").click() - self.assertVisibleBookmarks(["Bookmark 4", "Bookmark 5"]) + self.locate_bookmark("Bookmark 3").get_by_text("Archive").click() + self.assertVisibleBookmarks(["Bookmark 4", "Bookmark 5"]) - self.assertReloads(0) + self.assertReloads(0) def test_active_bookmarks_partial_update_on_archive(self): self.setup_fixture() - with sync_playwright() as p: - self.open(reverse("linkding:bookmarks.index"), p) + self.open(reverse("linkding:bookmarks.index")) - self.locate_bookmark("Bookmark 2").get_by_text("Archive").click() + self.locate_bookmark("Bookmark 2").get_by_text("Archive").click() - self.assertVisibleBookmarks(["Bookmark 1", "Bookmark 3"]) - self.assertVisibleTags(["Tag 1", "Tag 3"]) - self.assertReloads(0) + self.assertVisibleBookmarks(["Bookmark 1", "Bookmark 3"]) + self.assertVisibleTags(["Tag 1", "Tag 3"]) + self.assertReloads(0) def test_active_bookmarks_partial_update_on_delete(self): self.setup_fixture() - with sync_playwright() as p: - self.open(reverse("linkding:bookmarks.index"), p) + self.open(reverse("linkding:bookmarks.index")) - self.locate_bookmark("Bookmark 2").get_by_text("Remove").click() - self.locate_confirm_dialog().get_by_text("Confirm").click() + self.locate_bookmark("Bookmark 2").get_by_text("Remove").click() + self.locate_confirm_dialog().get_by_text("Confirm").click() - self.assertVisibleBookmarks(["Bookmark 1", "Bookmark 3"]) - self.assertVisibleTags(["Tag 1", "Tag 3"]) - self.assertReloads(0) + self.assertVisibleBookmarks(["Bookmark 1", "Bookmark 3"]) + self.assertVisibleTags(["Tag 1", "Tag 3"]) + self.assertReloads(0) def test_active_bookmarks_partial_update_on_mark_as_read(self): self.setup_fixture() @@ -135,15 +129,14 @@ class BookmarkPagePartialUpdatesE2ETestCase(LinkdingE2ETestCase): bookmark2.unread = True bookmark2.save() - with sync_playwright() as p: - self.open(reverse("linkding:bookmarks.index"), p) + self.open(reverse("linkding:bookmarks.index")) - expect(self.locate_bookmark("Bookmark 2")).to_have_class("unread") - self.locate_bookmark("Bookmark 2").get_by_text("Unread").click() - self.locate_confirm_dialog().get_by_text("Confirm").click() + expect(self.locate_bookmark("Bookmark 2")).to_have_class("unread") + self.locate_bookmark("Bookmark 2").get_by_text("Unread").click() + self.locate_confirm_dialog().get_by_text("Confirm").click() - expect(self.locate_bookmark("Bookmark 2")).not_to_have_class("unread") - self.assertReloads(0) + expect(self.locate_bookmark("Bookmark 2")).not_to_have_class("unread") + self.assertReloads(0) def test_active_bookmarks_partial_update_on_unshare(self): self.setup_fixture() @@ -151,112 +144,101 @@ class BookmarkPagePartialUpdatesE2ETestCase(LinkdingE2ETestCase): bookmark2.shared = True bookmark2.save() - with sync_playwright() as p: - self.open(reverse("linkding:bookmarks.index"), p) + self.open(reverse("linkding:bookmarks.index")) - expect(self.locate_bookmark("Bookmark 2")).to_have_class("shared") - self.locate_bookmark("Bookmark 2").get_by_text("Shared").click() - self.locate_confirm_dialog().get_by_text("Confirm").click() + expect(self.locate_bookmark("Bookmark 2")).to_have_class("shared") + self.locate_bookmark("Bookmark 2").get_by_text("Shared").click() + self.locate_confirm_dialog().get_by_text("Confirm").click() - expect(self.locate_bookmark("Bookmark 2")).not_to_have_class("shared") - self.assertReloads(0) + expect(self.locate_bookmark("Bookmark 2")).not_to_have_class("shared") + self.assertReloads(0) def test_active_bookmarks_partial_update_on_bulk_archive(self): self.setup_fixture() - with sync_playwright() as p: - self.open(reverse("linkding:bookmarks.index"), p) + self.open(reverse("linkding:bookmarks.index")) - self.locate_bulk_edit_toggle().click() - self.locate_bookmark("Bookmark 2").locator( - "label.bulk-edit-checkbox" - ).click() - self.select_bulk_action("Archive") - self.locate_bulk_edit_bar().get_by_text("Execute").click() - self.locate_confirm_dialog().get_by_text("Confirm").click() + self.locate_bulk_edit_toggle().click() + self.locate_bookmark("Bookmark 2").locator("label.bulk-edit-checkbox").click() + self.select_bulk_action("Archive") + self.locate_bulk_edit_bar().get_by_text("Execute").click() + self.locate_confirm_dialog().get_by_text("Confirm").click() - self.assertVisibleBookmarks(["Bookmark 1", "Bookmark 3"]) - self.assertVisibleTags(["Tag 1", "Tag 3"]) - self.assertReloads(0) + self.assertVisibleBookmarks(["Bookmark 1", "Bookmark 3"]) + self.assertVisibleTags(["Tag 1", "Tag 3"]) + self.assertReloads(0) def test_active_bookmarks_partial_update_on_bulk_delete(self): self.setup_fixture() - with sync_playwright() as p: - self.open(reverse("linkding:bookmarks.index"), p) + self.open(reverse("linkding:bookmarks.index")) - self.locate_bulk_edit_toggle().click() - self.locate_bookmark("Bookmark 2").locator( - "label.bulk-edit-checkbox" - ).click() - self.select_bulk_action("Delete") - self.locate_bulk_edit_bar().get_by_text("Execute").click() - self.locate_confirm_dialog().get_by_text("Confirm").click() + self.locate_bulk_edit_toggle().click() + self.locate_bookmark("Bookmark 2").locator("label.bulk-edit-checkbox").click() + self.select_bulk_action("Delete") + self.locate_bulk_edit_bar().get_by_text("Execute").click() + self.locate_confirm_dialog().get_by_text("Confirm").click() - self.assertVisibleBookmarks(["Bookmark 1", "Bookmark 3"]) - self.assertVisibleTags(["Tag 1", "Tag 3"]) - self.assertReloads(0) + self.assertVisibleBookmarks(["Bookmark 1", "Bookmark 3"]) + self.assertVisibleTags(["Tag 1", "Tag 3"]) + self.assertReloads(0) def test_archived_bookmarks_partial_update_on_unarchive(self): self.setup_fixture() - with sync_playwright() as p: - self.open(reverse("linkding:bookmarks.archived"), p) + self.open(reverse("linkding:bookmarks.archived")) - self.locate_bookmark("Archived Bookmark 2").get_by_text("Unarchive").click() + self.locate_bookmark("Archived Bookmark 2").get_by_text("Unarchive").click() - self.assertVisibleBookmarks(["Archived Bookmark 1", "Archived Bookmark 3"]) - self.assertVisibleTags(["Archived Tag 1", "Archived Tag 3"]) - self.assertReloads(0) + self.assertVisibleBookmarks(["Archived Bookmark 1", "Archived Bookmark 3"]) + self.assertVisibleTags(["Archived Tag 1", "Archived Tag 3"]) + self.assertReloads(0) def test_archived_bookmarks_partial_update_on_delete(self): self.setup_fixture() - with sync_playwright() as p: - self.open(reverse("linkding:bookmarks.archived"), p) + self.open(reverse("linkding:bookmarks.archived")) - self.locate_bookmark("Archived Bookmark 2").get_by_text("Remove").click() - self.locate_confirm_dialog().get_by_text("Confirm").click() + self.locate_bookmark("Archived Bookmark 2").get_by_text("Remove").click() + self.locate_confirm_dialog().get_by_text("Confirm").click() - self.assertVisibleBookmarks(["Archived Bookmark 1", "Archived Bookmark 3"]) - self.assertVisibleTags(["Archived Tag 1", "Archived Tag 3"]) - self.assertReloads(0) + self.assertVisibleBookmarks(["Archived Bookmark 1", "Archived Bookmark 3"]) + self.assertVisibleTags(["Archived Tag 1", "Archived Tag 3"]) + self.assertReloads(0) def test_archived_bookmarks_partial_update_on_bulk_unarchive(self): self.setup_fixture() - with sync_playwright() as p: - self.open(reverse("linkding:bookmarks.archived"), p) + self.open(reverse("linkding:bookmarks.archived")) - self.locate_bulk_edit_toggle().click() - self.locate_bookmark("Archived Bookmark 2").locator( - "label.bulk-edit-checkbox" - ).click() - self.select_bulk_action("Unarchive") - self.locate_bulk_edit_bar().get_by_text("Execute").click() - self.locate_confirm_dialog().get_by_text("Confirm").click() + self.locate_bulk_edit_toggle().click() + self.locate_bookmark("Archived Bookmark 2").locator( + "label.bulk-edit-checkbox" + ).click() + self.select_bulk_action("Unarchive") + self.locate_bulk_edit_bar().get_by_text("Execute").click() + self.locate_confirm_dialog().get_by_text("Confirm").click() - self.assertVisibleBookmarks(["Archived Bookmark 1", "Archived Bookmark 3"]) - self.assertVisibleTags(["Archived Tag 1", "Archived Tag 3"]) - self.assertReloads(0) + self.assertVisibleBookmarks(["Archived Bookmark 1", "Archived Bookmark 3"]) + self.assertVisibleTags(["Archived Tag 1", "Archived Tag 3"]) + self.assertReloads(0) def test_archived_bookmarks_partial_update_on_bulk_delete(self): self.setup_fixture() - with sync_playwright() as p: - self.open(reverse("linkding:bookmarks.archived"), p) + self.open(reverse("linkding:bookmarks.archived")) - self.locate_bulk_edit_toggle().click() - self.locate_bookmark("Archived Bookmark 2").locator( - "label.bulk-edit-checkbox" - ).click() - self.select_bulk_action("Delete") - self.locate_bulk_edit_bar().get_by_text("Execute").click() - self.locate_confirm_dialog().get_by_text("Confirm").click() + self.locate_bulk_edit_toggle().click() + self.locate_bookmark("Archived Bookmark 2").locator( + "label.bulk-edit-checkbox" + ).click() + self.select_bulk_action("Delete") + self.locate_bulk_edit_bar().get_by_text("Execute").click() + self.locate_confirm_dialog().get_by_text("Confirm").click() - self.assertVisibleBookmarks(["Archived Bookmark 1", "Archived Bookmark 3"]) - self.assertVisibleTags(["Archived Tag 1", "Archived Tag 3"]) - self.assertReloads(0) + self.assertVisibleBookmarks(["Archived Bookmark 1", "Archived Bookmark 3"]) + self.assertVisibleTags(["Archived Tag 1", "Archived Tag 3"]) + self.assertReloads(0) def test_shared_bookmarks_partial_update_on_unarchive(self): self.setup_fixture() @@ -264,24 +246,23 @@ class BookmarkPagePartialUpdatesE2ETestCase(LinkdingE2ETestCase): 3, shared=True, prefix="My Bookmark", with_tags=True ) - with sync_playwright() as p: - self.open(reverse("linkding:bookmarks.shared"), p) + self.open(reverse("linkding:bookmarks.shared")) - self.locate_bookmark("My Bookmark 2").get_by_text("Archive").click() + self.locate_bookmark("My Bookmark 2").get_by_text("Archive").click() - # Shared bookmarks page also shows archived bookmarks, though it probably shouldn't - self.assertVisibleBookmarks( - [ - "My Bookmark 1", - "My Bookmark 2", - "My Bookmark 3", - "Joe's Bookmark 1", - "Joe's Bookmark 2", - "Joe's Bookmark 3", - ] - ) - self.assertVisibleTags(["Shared Tag 1", "Shared Tag 2", "Shared Tag 3"]) - self.assertReloads(0) + # Shared bookmarks page also shows archived bookmarks, though it probably shouldn't + self.assertVisibleBookmarks( + [ + "My Bookmark 1", + "My Bookmark 2", + "My Bookmark 3", + "Joe's Bookmark 1", + "Joe's Bookmark 2", + "Joe's Bookmark 3", + ] + ) + self.assertVisibleTags(["Shared Tag 1", "Shared Tag 2", "Shared Tag 3"]) + self.assertReloads(0) def test_shared_bookmarks_partial_update_on_delete(self): self.setup_fixture() @@ -289,20 +270,19 @@ class BookmarkPagePartialUpdatesE2ETestCase(LinkdingE2ETestCase): 3, shared=True, prefix="My Bookmark", with_tags=True ) - with sync_playwright() as p: - self.open(reverse("linkding:bookmarks.shared"), p) + self.open(reverse("linkding:bookmarks.shared")) - self.locate_bookmark("My Bookmark 2").get_by_text("Remove").click() - self.locate_confirm_dialog().get_by_text("Confirm").click() + self.locate_bookmark("My Bookmark 2").get_by_text("Remove").click() + self.locate_confirm_dialog().get_by_text("Confirm").click() - self.assertVisibleBookmarks( - [ - "My Bookmark 1", - "My Bookmark 3", - "Joe's Bookmark 1", - "Joe's Bookmark 2", - "Joe's Bookmark 3", - ] - ) - self.assertVisibleTags(["Shared Tag 1", "Shared Tag 3"]) - self.assertReloads(0) + self.assertVisibleBookmarks( + [ + "My Bookmark 1", + "My Bookmark 3", + "Joe's Bookmark 1", + "Joe's Bookmark 2", + "Joe's Bookmark 3", + ] + ) + self.assertVisibleTags(["Shared Tag 1", "Shared Tag 3"]) + self.assertReloads(0) diff --git a/bookmarks/tests_e2e/e2e_test_bundle_preview.py b/bookmarks/tests_e2e/e2e_test_bundle_preview.py index 44e5b6e..11d098a 100644 --- a/bookmarks/tests_e2e/e2e_test_bundle_preview.py +++ b/bookmarks/tests_e2e/e2e_test_bundle_preview.py @@ -1,5 +1,5 @@ from django.urls import reverse -from playwright.sync_api import sync_playwright, expect +from playwright.sync_api import expect from bookmarks.tests_e2e.helpers import LinkdingE2ETestCase @@ -9,39 +9,38 @@ class BookmarkItemE2ETestCase(LinkdingE2ETestCase): group1 = self.setup_numbered_bookmarks(3, prefix="foo") group2 = self.setup_numbered_bookmarks(3, prefix="bar") - with sync_playwright() as p: - # shows all bookmarks initially - page = self.open(reverse("linkding:bundles.new"), p) + # shows all bookmarks initially + page = self.open(reverse("linkding:bundles.new")) - expect( - page.get_by_text(f"Found 6 bookmarks matching this bundle") - ).to_be_visible() - self.assertVisibleBookmarks(group1 + group2) + expect( + page.get_by_text(f"Found 6 bookmarks matching this bundle") + ).to_be_visible() + self.assertVisibleBookmarks(group1 + group2) - # filter by group1 - search = page.get_by_label("Search") - search.fill("foo") + # filter by group1 + search = page.get_by_label("Search") + search.fill("foo") - expect( - page.get_by_text(f"Found 3 bookmarks matching this bundle") - ).to_be_visible() - self.assertVisibleBookmarks(group1) + expect( + page.get_by_text(f"Found 3 bookmarks matching this bundle") + ).to_be_visible() + self.assertVisibleBookmarks(group1) - # filter by group2 - search.fill("bar") + # filter by group2 + search.fill("bar") - expect( - page.get_by_text(f"Found 3 bookmarks matching this bundle") - ).to_be_visible() - self.assertVisibleBookmarks(group2) + expect( + page.get_by_text(f"Found 3 bookmarks matching this bundle") + ).to_be_visible() + self.assertVisibleBookmarks(group2) - # filter by invalid group - search.fill("invalid") + # filter by invalid group + search.fill("invalid") - expect( - page.get_by_text(f"No bookmarks match the current bundle") - ).to_be_visible() - self.assertVisibleBookmarks([]) + expect( + page.get_by_text(f"No bookmarks match the current bundle") + ).to_be_visible() + self.assertVisibleBookmarks([]) def assertVisibleBookmarks(self, bookmarks): self.assertEqual(len(bookmarks), self.count_bookmarks()) diff --git a/bookmarks/tests_e2e/e2e_test_collapse_side_panel.py b/bookmarks/tests_e2e/e2e_test_collapse_side_panel.py index 569d98d..806398c 100644 --- a/bookmarks/tests_e2e/e2e_test_collapse_side_panel.py +++ b/bookmarks/tests_e2e/e2e_test_collapse_side_panel.py @@ -1,5 +1,5 @@ from django.urls import reverse -from playwright.sync_api import sync_playwright, expect +from playwright.sync_api import expect from bookmarks.tests_e2e.helpers import LinkdingE2ETestCase @@ -22,31 +22,25 @@ class CollapseSidePanelE2ETestCase(LinkdingE2ETestCase): ).to_be_visible() def test_side_panel_should_be_visible_by_default(self): - with sync_playwright() as p: - self.open(reverse("linkding:bookmarks.index"), p) - self.assertSidePanelIsVisible() + self.open(reverse("linkding:bookmarks.index")) + self.assertSidePanelIsVisible() - self.page.goto( - self.live_server_url + reverse("linkding:bookmarks.archived") - ) - self.assertSidePanelIsVisible() + self.page.goto(self.live_server_url + reverse("linkding:bookmarks.archived")) + self.assertSidePanelIsVisible() - self.page.goto(self.live_server_url + reverse("linkding:bookmarks.shared")) - self.assertSidePanelIsVisible() + self.page.goto(self.live_server_url + reverse("linkding:bookmarks.shared")) + self.assertSidePanelIsVisible() def test_side_panel_should_be_hidden_when_collapsed(self): user = self.get_or_create_test_user() user.profile.collapse_side_panel = True user.profile.save() - with sync_playwright() as p: - self.open(reverse("linkding:bookmarks.index"), p) - self.assertSidePanelIsHidden() + self.open(reverse("linkding:bookmarks.index")) + self.assertSidePanelIsHidden() - self.page.goto( - self.live_server_url + reverse("linkding:bookmarks.archived") - ) - self.assertSidePanelIsHidden() + self.page.goto(self.live_server_url + reverse("linkding:bookmarks.archived")) + self.assertSidePanelIsHidden() - self.page.goto(self.live_server_url + reverse("linkding:bookmarks.shared")) - self.assertSidePanelIsHidden() + self.page.goto(self.live_server_url + reverse("linkding:bookmarks.shared")) + self.assertSidePanelIsHidden() diff --git a/bookmarks/tests_e2e/e2e_test_dropdown.py b/bookmarks/tests_e2e/e2e_test_dropdown.py index 884bcce..2bb9e6c 100644 --- a/bookmarks/tests_e2e/e2e_test_dropdown.py +++ b/bookmarks/tests_e2e/e2e_test_dropdown.py @@ -1,5 +1,5 @@ from django.urls import reverse -from playwright.sync_api import sync_playwright, expect +from playwright.sync_api import expect from bookmarks.tests_e2e.helpers import LinkdingE2ETestCase @@ -18,111 +18,105 @@ class DropdownE2ETestCase(LinkdingE2ETestCase): return self.locate_dropdown().locator(".menu") def test_click_toggle_opens_and_closes_dropdown(self): - with sync_playwright() as p: - self.open(reverse("linkding:bookmarks.index"), p) + self.open(reverse("linkding:bookmarks.index")) - toggle = self.locate_dropdown_toggle() - menu = self.locate_dropdown_menu() + toggle = self.locate_dropdown_toggle() + menu = self.locate_dropdown_menu() - # Open dropdown - toggle.click() - expect(menu).to_be_visible() + # Open dropdown + toggle.click() + expect(menu).to_be_visible() - # Click toggle again to close - toggle.click() - expect(menu).not_to_be_visible() + # Click toggle again to close + toggle.click() + expect(menu).not_to_be_visible() def test_outside_click_closes_dropdown(self): - with sync_playwright() as p: - self.open(reverse("linkding:bookmarks.index"), p) + self.open(reverse("linkding:bookmarks.index")) - toggle = self.locate_dropdown_toggle() - menu = self.locate_dropdown_menu() + toggle = self.locate_dropdown_toggle() + menu = self.locate_dropdown_menu() - # Open dropdown - toggle.click() - expect(menu).to_be_visible() + # Open dropdown + toggle.click() + expect(menu).to_be_visible() - # Click outside the dropdown (on the page body) - self.page.locator("main").click() - expect(menu).not_to_be_visible() + # Click outside the dropdown (on the page body) + self.page.locator("main").click() + expect(menu).not_to_be_visible() def test_escape_closes_dropdown_and_restores_focus(self): - with sync_playwright() as p: - self.open(reverse("linkding:bookmarks.index"), p) + self.open(reverse("linkding:bookmarks.index")) - toggle = self.locate_dropdown_toggle() - menu = self.locate_dropdown_menu() + toggle = self.locate_dropdown_toggle() + menu = self.locate_dropdown_menu() - # Open dropdown - toggle.click() - expect(menu).to_be_visible() + # Open dropdown + toggle.click() + expect(menu).to_be_visible() - # Press Escape - self.page.keyboard.press("Escape") + # Press Escape + self.page.keyboard.press("Escape") - # Menu should be closed - expect(menu).not_to_be_visible() + # Menu should be closed + expect(menu).not_to_be_visible() - # Focus should be back on toggle - expect(toggle).to_be_focused() + # Focus should be back on toggle + expect(toggle).to_be_focused() def test_focus_out_closes_dropdown(self): - with sync_playwright() as p: - self.open(reverse("linkding:bookmarks.index"), p) + self.open(reverse("linkding:bookmarks.index")) - toggle = self.locate_dropdown_toggle() - menu = self.locate_dropdown_menu() + toggle = self.locate_dropdown_toggle() + menu = self.locate_dropdown_menu() - # Open dropdown - toggle.click() - expect(menu).to_be_visible() + # Open dropdown + toggle.click() + expect(menu).to_be_visible() - # Shift+Tab to move focus out of the dropdown - self.page.keyboard.press("Shift+Tab") + # Shift+Tab to move focus out of the dropdown + self.page.keyboard.press("Shift+Tab") - # Menu should be closed after focus leaves - expect(menu).not_to_be_visible() + # Menu should be closed after focus leaves + expect(menu).not_to_be_visible() def test_aria_expanded_attribute(self): - with sync_playwright() as p: - self.open(reverse("linkding:bookmarks.index"), p) + self.open(reverse("linkding:bookmarks.index")) - toggle = self.locate_dropdown_toggle() - menu = self.locate_dropdown_menu() + toggle = self.locate_dropdown_toggle() + menu = self.locate_dropdown_menu() - # Initially aria-expanded should be false - expect(toggle).to_have_attribute("aria-expanded", "false") + # Initially aria-expanded should be false + expect(toggle).to_have_attribute("aria-expanded", "false") - # Open dropdown - toggle.click() - expect(menu).to_be_visible() + # Open dropdown + toggle.click() + expect(menu).to_be_visible() - # aria-expanded should be true - expect(toggle).to_have_attribute("aria-expanded", "true") + # aria-expanded should be true + expect(toggle).to_have_attribute("aria-expanded", "true") - # Close dropdown - toggle.click() - expect(menu).not_to_be_visible() + # Close dropdown + toggle.click() + expect(menu).not_to_be_visible() - # aria-expanded should be false again - expect(toggle).to_have_attribute("aria-expanded", "false") + # aria-expanded should be false again + expect(toggle).to_have_attribute("aria-expanded", "false") def test_can_click_menu_item(self): - with sync_playwright() as p: - self.open(reverse("linkding:bookmarks.index"), p) + self.open(reverse("linkding:bookmarks.index")) - toggle = self.locate_dropdown_toggle() - menu = self.locate_dropdown_menu() + toggle = self.locate_dropdown_toggle() + menu = self.locate_dropdown_menu() - # Open dropdown - toggle.click() - expect(menu).to_be_visible() + # Open dropdown + toggle.click() + expect(menu).to_be_visible() - # Click on "Archived" menu item - menu.get_by_text("Archived", exact=True).click() + # Click on "Archived" menu item + menu.get_by_text("Archived", exact=True).click() - # Should navigate to archived page - expect(self.page).to_have_url( - self.live_server_url + reverse("linkding:bookmarks.archived") - ) + # Should navigate to archived page + expect(self.page).to_have_url( + self.live_server_url + reverse("linkding:bookmarks.archived") + ) diff --git a/bookmarks/tests_e2e/e2e_test_edit_bookmark_form.py b/bookmarks/tests_e2e/e2e_test_edit_bookmark_form.py index c1785da..f55ef27 100644 --- a/bookmarks/tests_e2e/e2e_test_edit_bookmark_form.py +++ b/bookmarks/tests_e2e/e2e_test_edit_bookmark_form.py @@ -1,7 +1,7 @@ from unittest.mock import patch from django.urls import reverse -from playwright.sync_api import sync_playwright, expect +from playwright.sync_api import expect from bookmarks.tests_e2e.helpers import LinkdingE2ETestCase from bookmarks.services import website_loader @@ -24,51 +24,47 @@ class BookmarkFormE2ETestCase(LinkdingE2ETestCase): self.website_loader_patch.start() def tearDown(self) -> None: - super().tearDown() self.website_loader_patch.stop() + super().tearDown() def test_should_not_check_for_existing_bookmark(self): bookmark = self.setup_bookmark() - with sync_playwright() as p: - page = self.open(reverse("linkding:bookmarks.edit", args=[bookmark.id]), p) + page = self.open(reverse("linkding:bookmarks.edit", args=[bookmark.id])) - page.wait_for_timeout(timeout=1000) - page.get_by_text("This URL is already bookmarked.").wait_for(state="hidden") + page.wait_for_timeout(timeout=1000) + page.get_by_text("This URL is already bookmarked.").wait_for(state="hidden") def test_should_not_prefill_title_and_description(self): bookmark = self.setup_bookmark( title="Initial title", description="Initial description" ) - with sync_playwright() as p: - page = self.open(reverse("linkding:bookmarks.edit", args=[bookmark.id]), p) - page.wait_for_timeout(timeout=1000) + page = self.open(reverse("linkding:bookmarks.edit", args=[bookmark.id])) + page.wait_for_timeout(timeout=1000) - title = page.get_by_label("Title") - description = page.get_by_label("Description") - expect(title).to_have_value(bookmark.title) - expect(description).to_have_value(bookmark.description) + title = page.get_by_label("Title") + description = page.get_by_label("Description") + expect(title).to_have_value(bookmark.title) + expect(description).to_have_value(bookmark.description) def test_enter_url_should_not_prefill_title_and_description(self): bookmark = self.setup_bookmark() - with sync_playwright() as p: - page = self.open(reverse("linkding:bookmarks.edit", args=[bookmark.id]), p) + page = self.open(reverse("linkding:bookmarks.edit", args=[bookmark.id])) - page.get_by_label("URL").fill("https://example.com") - page.wait_for_timeout(timeout=1000) + page.get_by_label("URL").fill("https://example.com") + page.wait_for_timeout(timeout=1000) - title = page.get_by_label("Title") - description = page.get_by_label("Description") - expect(title).to_have_value(bookmark.title) - expect(description).to_have_value(bookmark.description) + title = page.get_by_label("Title") + description = page.get_by_label("Description") + expect(title).to_have_value(bookmark.title) + expect(description).to_have_value(bookmark.description) def test_refresh_button_should_be_visible_when_editing(self): bookmark = self.setup_bookmark() - with sync_playwright() as p: - page = self.open(reverse("linkding:bookmarks.edit", args=[bookmark.id]), p) + page = self.open(reverse("linkding:bookmarks.edit", args=[bookmark.id])) - refresh_button = page.get_by_text("Refresh from website") - expect(refresh_button).to_be_visible() + refresh_button = page.get_by_text("Refresh from website") + expect(refresh_button).to_be_visible() diff --git a/bookmarks/tests_e2e/e2e_test_filter_drawer.py b/bookmarks/tests_e2e/e2e_test_filter_drawer.py index 51a4e7f..427f2a4 100644 --- a/bookmarks/tests_e2e/e2e_test_filter_drawer.py +++ b/bookmarks/tests_e2e/e2e_test_filter_drawer.py @@ -1,5 +1,5 @@ from django.urls import reverse -from playwright.sync_api import sync_playwright, expect +from playwright.sync_api import expect from bookmarks.tests_e2e.helpers import LinkdingE2ETestCase @@ -9,62 +9,60 @@ class FilterDrawerE2ETestCase(LinkdingE2ETestCase): self.setup_bookmark(tags=[self.setup_tag(name="cooking")]) self.setup_bookmark(tags=[self.setup_tag(name="hiking")]) - with sync_playwright() as p: - page = self.open(reverse("linkding:bookmarks.index"), p) + page = self.open(reverse("linkding:bookmarks.index")) - # use smaller viewport to make filter button visible - page.set_viewport_size({"width": 375, "height": 812}) + # use smaller viewport to make filter button visible + page.set_viewport_size({"width": 375, "height": 812}) - # open drawer - drawer_trigger = page.locator(".main").get_by_role("button", name="Filters") - drawer_trigger.click() + # open drawer + drawer_trigger = page.locator(".main").get_by_role("button", name="Filters") + drawer_trigger.click() - # verify drawer is visible - drawer = page.locator("ld-filter-drawer") - expect(drawer).to_be_visible() - expect(drawer.locator("h2")).to_have_text("Filters") + # verify drawer is visible + drawer = page.locator("ld-filter-drawer") + expect(drawer).to_be_visible() + expect(drawer.locator("h2")).to_have_text("Filters") - # close with close button - drawer.locator("button.close").click() - expect(drawer).to_be_hidden() + # close with close button + drawer.locator("button.close").click() + expect(drawer).to_be_hidden() - # open drawer again - drawer_trigger.click() + # open drawer again + drawer_trigger.click() - # close with backdrop - backdrop = drawer.locator(".modal-overlay") - backdrop.click(position={"x": 0, "y": 0}) - expect(drawer).to_be_hidden() + # close with backdrop + backdrop = drawer.locator(".modal-overlay") + backdrop.click(position={"x": 0, "y": 0}) + expect(drawer).to_be_hidden() def test_select_tag(self): self.setup_bookmark(tags=[self.setup_tag(name="cooking")]) self.setup_bookmark(tags=[self.setup_tag(name="hiking")]) - with sync_playwright() as p: - page = self.open(reverse("linkding:bookmarks.index"), p) + page = self.open(reverse("linkding:bookmarks.index")) - # use smaller viewport to make filter button visible - page.set_viewport_size({"width": 375, "height": 812}) + # use smaller viewport to make filter button visible + page.set_viewport_size({"width": 375, "height": 812}) - # open tag cloud modal - drawer_trigger = page.locator(".main").get_by_role("button", name="Filters") - drawer_trigger.click() + # open tag cloud modal + drawer_trigger = page.locator(".main").get_by_role("button", name="Filters") + drawer_trigger.click() - # verify tags are displayed - drawer = page.locator("ld-filter-drawer") - unselected_tags = drawer.locator(".unselected-tags") - expect(unselected_tags.get_by_text("cooking")).to_be_visible() - expect(unselected_tags.get_by_text("hiking")).to_be_visible() + # verify tags are displayed + drawer = page.locator("ld-filter-drawer") + unselected_tags = drawer.locator(".unselected-tags") + expect(unselected_tags.get_by_text("cooking")).to_be_visible() + expect(unselected_tags.get_by_text("hiking")).to_be_visible() - # select tag - unselected_tags.get_by_text("cooking").click() + # select tag + unselected_tags.get_by_text("cooking").click() - # open drawer again - drawer_trigger.click() + # open drawer again + drawer_trigger.click() - # verify tag is selected, other tag is not visible anymore - selected_tags = drawer.locator(".selected-tags") - expect(selected_tags.get_by_text("cooking")).to_be_visible() + # verify tag is selected, other tag is not visible anymore + selected_tags = drawer.locator(".selected-tags") + expect(selected_tags.get_by_text("cooking")).to_be_visible() - expect(unselected_tags.get_by_text("cooking")).not_to_be_visible() - expect(unselected_tags.get_by_text("hiking")).not_to_be_visible() + expect(unselected_tags.get_by_text("cooking")).not_to_be_visible() + expect(unselected_tags.get_by_text("hiking")).not_to_be_visible() diff --git a/bookmarks/tests_e2e/e2e_test_global_shortcuts.py b/bookmarks/tests_e2e/e2e_test_global_shortcuts.py index 15a67ed..ab105bf 100644 --- a/bookmarks/tests_e2e/e2e_test_global_shortcuts.py +++ b/bookmarks/tests_e2e/e2e_test_global_shortcuts.py @@ -1,32 +1,24 @@ from django.urls import reverse -from playwright.sync_api import sync_playwright, expect +from playwright.sync_api import expect from bookmarks.tests_e2e.helpers import LinkdingE2ETestCase class GlobalShortcutsE2ETestCase(LinkdingE2ETestCase): def test_focus_search(self): - with sync_playwright() as p: - browser = self.setup_browser(p) - page = browser.new_page() - page.goto(self.live_server_url + reverse("linkding:bookmarks.index")) + self.open(reverse("linkding:bookmarks.index")) - page.press("body", "s") + self.page.press("body", "s") - expect(page.get_by_placeholder("Search for words or #tags")).to_be_focused() - - browser.close() + expect( + self.page.get_by_placeholder("Search for words or #tags") + ).to_be_focused() def test_add_bookmark(self): - with sync_playwright() as p: - browser = self.setup_browser(p) - page = browser.new_page() - page.goto(self.live_server_url + reverse("linkding:bookmarks.index")) + self.open(reverse("linkding:bookmarks.index")) - page.press("body", "n") + self.page.press("body", "n") - expect(page).to_have_url( - self.live_server_url + reverse("linkding:bookmarks.new") - ) - - browser.close() + expect(self.page).to_have_url( + self.live_server_url + reverse("linkding:bookmarks.new") + ) diff --git a/bookmarks/tests_e2e/e2e_test_new_bookmark_form.py b/bookmarks/tests_e2e/e2e_test_new_bookmark_form.py index c95d3a4..30481ac 100644 --- a/bookmarks/tests_e2e/e2e_test_new_bookmark_form.py +++ b/bookmarks/tests_e2e/e2e_test_new_bookmark_form.py @@ -2,7 +2,7 @@ from unittest.mock import patch from urllib.parse import quote from django.urls import reverse -from playwright.sync_api import sync_playwright, expect +from playwright.sync_api import expect from bookmarks.models import Bookmark from bookmarks.services import website_loader @@ -26,74 +26,69 @@ class BookmarkFormE2ETestCase(LinkdingE2ETestCase): self.website_loader_mock = self.website_loader_patch.start() def tearDown(self) -> None: - super().tearDown() self.website_loader_patch.stop() + super().tearDown() def test_enter_url_prefills_title_and_description(self): - with sync_playwright() as p: - page = self.open(reverse("linkding:bookmarks.new"), p) - url = page.get_by_label("URL") - title = page.get_by_label("Title") - description = page.get_by_label("Description") + page = self.open(reverse("linkding:bookmarks.new")) + url = page.get_by_label("URL") + title = page.get_by_label("Title") + description = page.get_by_label("Description") - url.fill("https://example.com") - expect(title).to_have_value("Example Domain") - expect(description).to_have_value( - "This domain is for use in illustrative examples in documents. You may use this domain in literature without prior coordination or asking for permission." - ) + url.fill("https://example.com") + expect(title).to_have_value("Example Domain") + expect(description).to_have_value( + "This domain is for use in illustrative examples in documents. You may use this domain in literature without prior coordination or asking for permission." + ) def test_enter_url_does_not_overwrite_modified_title_and_description(self): - with sync_playwright() as p: - page = self.open(reverse("linkding:bookmarks.new"), p) - url = page.get_by_label("URL") - title = page.get_by_label("Title") - description = page.get_by_label("Description") + page = self.open(reverse("linkding:bookmarks.new")) + url = page.get_by_label("URL") + title = page.get_by_label("Title") + description = page.get_by_label("Description") - title.fill("Modified title") - description.fill("Modified description") - url.fill("https://example.com") - page.wait_for_timeout(timeout=1000) + title.fill("Modified title") + description.fill("Modified description") + url.fill("https://example.com") + page.wait_for_timeout(timeout=1000) - expect(title).to_have_value("Modified title") - expect(description).to_have_value("Modified description") + expect(title).to_have_value("Modified title") + expect(description).to_have_value("Modified description") def test_with_initial_url_prefills_title_and_description(self): - with sync_playwright() as p: - page_url = ( - reverse("linkding:bookmarks.new") - + f"?url={quote('https://example.com')}" - ) - page = self.open(page_url, p) - url = page.get_by_label("URL") - title = page.get_by_label("Title") - description = page.get_by_label("Description") + page_url = ( + reverse("linkding:bookmarks.new") + f"?url={quote('https://example.com')}" + ) + page = self.open(page_url) + url = page.get_by_label("URL") + title = page.get_by_label("Title") + description = page.get_by_label("Description") - page.wait_for_timeout(timeout=1000) + page.wait_for_timeout(timeout=1000) - expect(url).to_have_value("https://example.com") - expect(title).to_have_value("Example Domain") - expect(description).to_have_value( - "This domain is for use in illustrative examples in documents. You may use this domain in literature without prior coordination or asking for permission." - ) + expect(url).to_have_value("https://example.com") + expect(title).to_have_value("Example Domain") + expect(description).to_have_value( + "This domain is for use in illustrative examples in documents. You may use this domain in literature without prior coordination or asking for permission." + ) def test_with_initial_url_title_description_does_not_overwrite_title_and_description( self, ): - with sync_playwright() as p: - page_url = ( - reverse("linkding:bookmarks.new") - + f"?url={quote('https://example.com')}&title=Initial+title&description=Initial+description" - ) - page = self.open(page_url, p) - url = page.get_by_label("URL") - title = page.get_by_label("Title") - description = page.get_by_label("Description") + page_url = ( + reverse("linkding:bookmarks.new") + + f"?url={quote('https://example.com')}&title=Initial+title&description=Initial+description" + ) + page = self.open(page_url) + url = page.get_by_label("URL") + title = page.get_by_label("Title") + description = page.get_by_label("Description") - page.wait_for_timeout(timeout=1000) + page.wait_for_timeout(timeout=1000) - expect(url).to_have_value("https://example.com") - expect(title).to_have_value("Initial title") - expect(description).to_have_value("Initial description") + expect(url).to_have_value("https://example.com") + expect(title).to_have_value("Initial title") + expect(description).to_have_value("Initial description") def test_create_should_check_for_existing_bookmark(self): existing_bookmark = self.setup_bookmark( @@ -105,170 +100,164 @@ class BookmarkFormE2ETestCase(LinkdingE2ETestCase): ) tag_names = " ".join(existing_bookmark.tag_names) - with sync_playwright() as p: - page = self.open(reverse("linkding:bookmarks.new"), p) + page = self.open(reverse("linkding:bookmarks.new")) - # Enter bookmarked URL - page.get_by_label("URL").fill(existing_bookmark.url) - # Already bookmarked hint should be visible - page.get_by_text("This URL is already bookmarked.").wait_for(timeout=2000) - # Form should be pre-filled with data from existing bookmark - self.assertEqual( - existing_bookmark.title, page.get_by_label("Title").input_value() - ) - self.assertEqual( - existing_bookmark.description, - page.get_by_label("Description").input_value(), - ) - self.assertEqual( - existing_bookmark.notes, page.get_by_label("Notes").input_value() - ) - self.assertEqual(tag_names, page.get_by_label("Tags").input_value()) - self.assertTrue(tag_names, page.get_by_label("Mark as unread").is_checked()) + # Enter bookmarked URL + page.get_by_label("URL").fill(existing_bookmark.url) + # Already bookmarked hint should be visible + page.get_by_text("This URL is already bookmarked.").wait_for(timeout=2000) + # Form should be pre-filled with data from existing bookmark + self.assertEqual( + existing_bookmark.title, page.get_by_label("Title").input_value() + ) + self.assertEqual( + existing_bookmark.description, + page.get_by_label("Description").input_value(), + ) + self.assertEqual( + existing_bookmark.notes, page.get_by_label("Notes").input_value() + ) + self.assertEqual(tag_names, page.get_by_label("Tags").input_value()) + self.assertTrue(tag_names, page.get_by_label("Mark as unread").is_checked()) - # Enter non-bookmarked URL - page.get_by_label("URL").fill("https://example.com/unknown") - # Already bookmarked hint should be hidden - page.get_by_text("This URL is already bookmarked.").wait_for( - state="hidden", timeout=2000 - ) + # Enter non-bookmarked URL + page.get_by_label("URL").fill("https://example.com/unknown") + # Already bookmarked hint should be hidden + page.get_by_text("This URL is already bookmarked.").wait_for( + state="hidden", timeout=2000 + ) def test_enter_url_of_existing_bookmark_should_show_notes(self): bookmark = self.setup_bookmark( notes="Existing notes", description="Existing description" ) - with sync_playwright() as p: - page = self.open(reverse("linkding:bookmarks.new"), p) + page = self.open(reverse("linkding:bookmarks.new")) - details = page.locator("details.notes") - expect(details).not_to_have_attribute("open", value="") + details = page.locator("details.notes") + expect(details).not_to_have_attribute("open", value="") - page.get_by_label("URL").fill(bookmark.url) - expect(details).to_have_attribute("open", value="") + page.get_by_label("URL").fill(bookmark.url) + expect(details).to_have_attribute("open", value="") def test_create_should_preview_auto_tags(self): profile = self.get_or_create_test_user().profile profile.auto_tagging_rules = "github.com dev github" profile.save() - with sync_playwright() as p: - # Open page with URL that should have auto tags - url = ( - reverse("linkding:bookmarks.new") - + "?url=https%3A%2F%2Fgithub.com%2Fsissbruecker%2Flinkding" - ) - page = self.open(url, p) + # Open page with URL that should have auto tags + url = ( + reverse("linkding:bookmarks.new") + + "?url=https%3A%2F%2Fgithub.com%2Fsissbruecker%2Flinkding" + ) + page = self.open(url) - auto_tags_hint = page.locator(".form-input-hint.auto-tags") - expect(auto_tags_hint).to_be_visible() - expect(auto_tags_hint).to_have_text("Auto tags: dev github") + auto_tags_hint = page.locator(".form-input-hint.auto-tags") + expect(auto_tags_hint).to_be_visible() + expect(auto_tags_hint).to_have_text("Auto tags: dev github") - # Change to URL without auto tags - page.get_by_label("URL").fill("https://example.com") + # Change to URL without auto tags + page.get_by_label("URL").fill("https://example.com") - expect(auto_tags_hint).to_be_hidden() + expect(auto_tags_hint).to_be_hidden() def test_clear_buttons_only_shown_when_fields_have_content(self): - with sync_playwright() as p: - page = self.open(reverse("linkding:bookmarks.new"), p) + page = self.open(reverse("linkding:bookmarks.new")) - title_field = page.get_by_label("Title") - title_clear_button = page.locator("ld-clear-button[data-for='id_title']") - description_field = page.get_by_label("Description") - description_clear_button = page.locator( - "ld-clear-button[data-for='id_description']" - ) + title_field = page.get_by_label("Title") + title_clear_button = page.locator("ld-clear-button[data-for='id_title']") + description_field = page.get_by_label("Description") + description_clear_button = page.locator( + "ld-clear-button[data-for='id_description']" + ) - # Initially, clear buttons should be hidden because fields are empty - expect(title_clear_button).to_be_hidden() - expect(description_clear_button).to_be_hidden() + # Initially, clear buttons should be hidden because fields are empty + expect(title_clear_button).to_be_hidden() + expect(description_clear_button).to_be_hidden() - # Add content to title field, its clear button should become visible - title_field.fill("Test title") - expect(title_clear_button).to_be_visible() + # Add content to title field, its clear button should become visible + title_field.fill("Test title") + expect(title_clear_button).to_be_visible() - # Add content to description field, its clear button should become visible - description_field.fill("Test description") - expect(description_clear_button).to_be_visible() + # Add content to description field, its clear button should become visible + description_field.fill("Test description") + expect(description_clear_button).to_be_visible() - # Clear title field, its clear button should be hidden again - title_field.fill("") - expect(title_clear_button).to_be_hidden() + # Clear title field, its clear button should be hidden again + title_field.fill("") + expect(title_clear_button).to_be_hidden() - # Clear description field, its clear button should be hidden again - description_field.fill("") - expect(description_clear_button).to_be_hidden() + # Clear description field, its clear button should be hidden again + description_field.fill("") + expect(description_clear_button).to_be_hidden() def test_refresh_button_only_shown_for_existing_bookmarks(self): existing_bookmark = self.setup_bookmark( title="Existing title", description="Existing description" ) - with sync_playwright() as p: - page = self.open(reverse("linkding:bookmarks.new"), p) - refresh_button = page.locator("#refresh-button") + page = self.open(reverse("linkding:bookmarks.new")) + refresh_button = page.locator("#refresh-button") - # Initially, refresh button should be hidden - expect(refresh_button).to_be_hidden() + # Initially, refresh button should be hidden + expect(refresh_button).to_be_hidden() - # Enter a URL that is not bookmarked yet - page.get_by_label("URL").fill("https://example.com/not-bookmarked") - page.wait_for_timeout(timeout=1000) + # Enter a URL that is not bookmarked yet + page.get_by_label("URL").fill("https://example.com/not-bookmarked") + page.wait_for_timeout(timeout=1000) - expect(refresh_button).to_be_hidden() + expect(refresh_button).to_be_hidden() - # Enter a URL that is already bookmarked - page.get_by_label("URL").fill(existing_bookmark.url) + # Enter a URL that is already bookmarked + page.get_by_label("URL").fill(existing_bookmark.url) - expect(refresh_button).to_be_visible() + expect(refresh_button).to_be_visible() - # Change back to non-bookmarked URL - page.get_by_label("URL").fill("https://example.com/another-not-bookmarked") + # Change back to non-bookmarked URL + page.get_by_label("URL").fill("https://example.com/another-not-bookmarked") - expect(refresh_button).to_be_hidden() + expect(refresh_button).to_be_hidden() def test_refresh_from_website_button_updates_title_and_description(self): existing_bookmark = self.setup_bookmark( title="Existing title", description="Existing description" ) - with sync_playwright() as p: - page = self.open(reverse("linkding:bookmarks.new"), p) - url_field = page.get_by_label("URL") - title_field = page.get_by_label("Title") - description_field = page.get_by_label("Description") - refresh_button = page.locator("#refresh-button") + page = self.open(reverse("linkding:bookmarks.new")) + url_field = page.get_by_label("URL") + title_field = page.get_by_label("Title") + description_field = page.get_by_label("Description") + refresh_button = page.locator("#refresh-button") - # Enter the URL of the existing bookmark to make refresh button visible - url_field.fill(existing_bookmark.url) + # Enter the URL of the existing bookmark to make refresh button visible + url_field.fill(existing_bookmark.url) - # Wait for metadata to be loaded and for refresh button to be visible - expect(refresh_button).to_be_visible() - expect(title_field).to_have_value("Existing title") - expect(description_field).to_have_value("Existing description") + # Wait for metadata to be loaded and for refresh button to be visible + expect(refresh_button).to_be_visible() + expect(title_field).to_have_value("Existing title") + expect(description_field).to_have_value("Existing description") - # Update the mock to return different metadata when refresh is requested - self.website_loader_mock.reset_mock() - self.website_loader_mock.return_value = website_loader.WebsiteMetadata( - url="https://example.com", - title="Updated Example Domain", - description="This is a refreshed description for the example domain.", - preview_image=None, - ) + # Update the mock to return different metadata when refresh is requested + self.website_loader_mock.reset_mock() + self.website_loader_mock.return_value = website_loader.WebsiteMetadata( + url="https://example.com", + title="Updated Example Domain", + description="This is a refreshed description for the example domain.", + preview_image=None, + ) - # Click the refresh button - refresh_button.click() + # Click the refresh button + refresh_button.click() - # Verify that title and description have been updated with new values - expect(title_field).to_have_value("Updated Example Domain") - expect(description_field).to_have_value( - "This is a refreshed description for the example domain." - ) + # Verify that title and description have been updated with new values + expect(title_field).to_have_value("Updated Example Domain") + expect(description_field).to_have_value( + "This is a refreshed description for the example domain." + ) - # Verify that the fields are visually marked as modified - expect(title_field).to_have_class("form-input modified") - expect(description_field).to_have_class("form-input modified") + # Verify that the fields are visually marked as modified + expect(title_field).to_have_class("form-input modified") + expect(description_field).to_have_class("form-input modified") def test_refresh_from_website_button_does_not_modify_fields_if_metadata_is_same( self, @@ -277,59 +266,57 @@ class BookmarkFormE2ETestCase(LinkdingE2ETestCase): title="Existing title", description="Existing description" ) - with sync_playwright() as p: - page = self.open(reverse("linkding:bookmarks.new"), p) - url_field = page.get_by_label("URL") - title_field = page.get_by_label("Title") - description_field = page.get_by_label("Description") - refresh_button = page.locator("#refresh-button") + page = self.open(reverse("linkding:bookmarks.new")) + url_field = page.get_by_label("URL") + title_field = page.get_by_label("Title") + description_field = page.get_by_label("Description") + refresh_button = page.locator("#refresh-button") - # Enter the URL of the existing bookmark to make refresh button visible - url_field.fill(existing_bookmark.url) + # Enter the URL of the existing bookmark to make refresh button visible + url_field.fill(existing_bookmark.url) - # Wait for metadata to be loaded and for refresh button to be visible - expect(refresh_button).to_be_visible() - expect(title_field).to_have_value("Existing title") - expect(description_field).to_have_value("Existing description") + # Wait for metadata to be loaded and for refresh button to be visible + expect(refresh_button).to_be_visible() + expect(title_field).to_have_value("Existing title") + expect(description_field).to_have_value("Existing description") - # Update the mock to return same metadata when refresh is requested - self.website_loader_mock.reset_mock() - self.website_loader_mock.return_value = website_loader.WebsiteMetadata( - url="https://example.com", - title="Existing title", - description="Existing description", - preview_image=None, - ) + # Update the mock to return same metadata when refresh is requested + self.website_loader_mock.reset_mock() + self.website_loader_mock.return_value = website_loader.WebsiteMetadata( + url="https://example.com", + title="Existing title", + description="Existing description", + preview_image=None, + ) - # Click the refresh button - refresh_button.click() - page.wait_for_timeout(timeout=1000) + # Click the refresh button + refresh_button.click() + page.wait_for_timeout(timeout=1000) - # Verify that title and description values are still the same - expect(title_field).to_have_value("Existing title") - expect(description_field).to_have_value("Existing description") + # Verify that title and description values are still the same + expect(title_field).to_have_value("Existing title") + expect(description_field).to_have_value("Existing description") - # Verify that the fields are NOT visually marked as modified - expect(title_field).to_have_class("form-input") - expect(description_field).to_have_class("form-input") + # Verify that the fields are NOT visually marked as modified + expect(title_field).to_have_class("form-input") + expect(description_field).to_have_class("form-input") def test_ctrl_enter_submits_form_from_description(self): - with sync_playwright() as p: - page = self.open(reverse("linkding:bookmarks.new"), p) - url_field = page.get_by_label("URL") - description_field = page.get_by_label("Description") + page = self.open(reverse("linkding:bookmarks.new")) + url_field = page.get_by_label("URL") + description_field = page.get_by_label("Description") - url_field.fill("https://example.com") - description_field.fill("Test description") - description_field.focus() + url_field.fill("https://example.com") + description_field.fill("Test description") + description_field.focus() - # Press Ctrl+Enter to submit form - description_field.press("Control+Enter") + # Press Ctrl+Enter to submit form + description_field.press("Control+Enter") - # Should navigate away from new bookmark page after successful submission - expect(page).not_to_have_url( - self.live_server_url + reverse("linkding:bookmarks.new") - ) + # Should navigate away from new bookmark page after successful submission + expect(page).not_to_have_url( + self.live_server_url + reverse("linkding:bookmarks.new") + ) self.assertEqual(1, Bookmark.objects.count()) bookmark = Bookmark.objects.first() diff --git a/bookmarks/tests_e2e/e2e_test_settings_general.py b/bookmarks/tests_e2e/e2e_test_settings_general.py index fafe28b..193aac1 100644 --- a/bookmarks/tests_e2e/e2e_test_settings_general.py +++ b/bookmarks/tests_e2e/e2e_test_settings_general.py @@ -1,5 +1,5 @@ from django.urls import reverse -from playwright.sync_api import sync_playwright, expect +from playwright.sync_api import expect from bookmarks.tests_e2e.helpers import LinkdingE2ETestCase from bookmarks.models import UserProfile @@ -7,54 +7,51 @@ from bookmarks.models import UserProfile class SettingsGeneralE2ETestCase(LinkdingE2ETestCase): def test_should_only_enable_public_sharing_if_sharing_is_enabled(self): - with sync_playwright() as p: - browser = self.setup_browser(p) - page = browser.new_page() - page.goto(self.live_server_url + reverse("linkding:settings.general")) + self.open(reverse("linkding:settings.general")) - enable_sharing = page.get_by_label("Enable bookmark sharing") - enable_sharing_label = page.get_by_text("Enable bookmark sharing") - enable_public_sharing = page.get_by_label("Enable public bookmark sharing") - enable_public_sharing_label = page.get_by_text( - "Enable public bookmark sharing" - ) - default_mark_shared = page.get_by_label( - "Create bookmarks as shared by default" - ) - default_mark_shared_label = page.get_by_text( - "Create bookmarks as shared by default" - ) + enable_sharing = self.page.get_by_label("Enable bookmark sharing") + enable_sharing_label = self.page.get_by_text("Enable bookmark sharing") + enable_public_sharing = self.page.get_by_label("Enable public bookmark sharing") + enable_public_sharing_label = self.page.get_by_text( + "Enable public bookmark sharing" + ) + default_mark_shared = self.page.get_by_label( + "Create bookmarks as shared by default" + ) + default_mark_shared_label = self.page.get_by_text( + "Create bookmarks as shared by default" + ) - # Public sharing and default shared are disabled by default - expect(enable_sharing).not_to_be_checked() - expect(enable_public_sharing).not_to_be_checked() - expect(enable_public_sharing).to_be_disabled() - expect(default_mark_shared).not_to_be_checked() - expect(default_mark_shared).to_be_disabled() + # Public sharing and default shared are disabled by default + expect(enable_sharing).not_to_be_checked() + expect(enable_public_sharing).not_to_be_checked() + expect(enable_public_sharing).to_be_disabled() + expect(default_mark_shared).not_to_be_checked() + expect(default_mark_shared).to_be_disabled() - # Enable sharing - enable_sharing_label.click() - expect(enable_sharing).to_be_checked() - expect(enable_public_sharing).not_to_be_checked() - expect(enable_public_sharing).to_be_enabled() - expect(default_mark_shared).not_to_be_checked() - expect(default_mark_shared).to_be_enabled() + # Enable sharing + enable_sharing_label.click() + expect(enable_sharing).to_be_checked() + expect(enable_public_sharing).not_to_be_checked() + expect(enable_public_sharing).to_be_enabled() + expect(default_mark_shared).not_to_be_checked() + expect(default_mark_shared).to_be_enabled() - # Enable public sharing and default shared - enable_public_sharing_label.click() - default_mark_shared_label.click() - expect(enable_public_sharing).to_be_checked() - expect(enable_public_sharing).to_be_enabled() - expect(default_mark_shared).to_be_checked() - expect(default_mark_shared).to_be_enabled() + # Enable public sharing and default shared + enable_public_sharing_label.click() + default_mark_shared_label.click() + expect(enable_public_sharing).to_be_checked() + expect(enable_public_sharing).to_be_enabled() + expect(default_mark_shared).to_be_checked() + expect(default_mark_shared).to_be_enabled() - # Disable sharing - enable_sharing_label.click() - expect(enable_sharing).not_to_be_checked() - expect(enable_public_sharing).not_to_be_checked() - expect(enable_public_sharing).to_be_disabled() - expect(default_mark_shared).not_to_be_checked() - expect(default_mark_shared).to_be_disabled() + # Disable sharing + enable_sharing_label.click() + expect(enable_sharing).not_to_be_checked() + expect(enable_public_sharing).not_to_be_checked() + expect(enable_public_sharing).to_be_disabled() + expect(default_mark_shared).not_to_be_checked() + expect(default_mark_shared).to_be_disabled() def test_should_not_show_bookmark_description_max_lines_when_display_inline(self): profile = self.get_or_create_test_user().profile @@ -63,13 +60,10 @@ class SettingsGeneralE2ETestCase(LinkdingE2ETestCase): ) profile.save() - with sync_playwright() as p: - browser = self.setup_browser(p) - page = browser.new_page() - page.goto(self.live_server_url + reverse("linkding:settings.general")) + self.open(reverse("linkding:settings.general")) - max_lines = page.get_by_label("Bookmark description max lines") - expect(max_lines).to_be_hidden() + max_lines = self.page.get_by_label("Bookmark description max lines") + expect(max_lines).to_be_hidden() def test_should_show_bookmark_description_max_lines_when_display_separate(self): profile = self.get_or_create_test_user().profile @@ -78,26 +72,20 @@ class SettingsGeneralE2ETestCase(LinkdingE2ETestCase): ) profile.save() - with sync_playwright() as p: - browser = self.setup_browser(p) - page = browser.new_page() - page.goto(self.live_server_url + reverse("linkding:settings.general")) + self.open(reverse("linkding:settings.general")) - max_lines = page.get_by_label("Bookmark description max lines") - expect(max_lines).to_be_visible() + max_lines = self.page.get_by_label("Bookmark description max lines") + expect(max_lines).to_be_visible() def test_should_update_bookmark_description_max_lines_when_changing_display(self): - with sync_playwright() as p: - browser = self.setup_browser(p) - page = browser.new_page() - page.goto(self.live_server_url + reverse("linkding:settings.general")) + self.open(reverse("linkding:settings.general")) - max_lines = page.get_by_label("Bookmark description max lines") - expect(max_lines).to_be_hidden() + max_lines = self.page.get_by_label("Bookmark description max lines") + expect(max_lines).to_be_hidden() - display = page.get_by_label("Bookmark description", exact=True) - display.select_option("separate") - expect(max_lines).to_be_visible() + display = self.page.get_by_label("Bookmark description", exact=True) + display.select_option("separate") + expect(max_lines).to_be_visible() - display.select_option("inline") - expect(max_lines).to_be_hidden() + display.select_option("inline") + expect(max_lines).to_be_hidden() diff --git a/bookmarks/tests_e2e/e2e_test_settings_integrations.py b/bookmarks/tests_e2e/e2e_test_settings_integrations.py index cd50b0f..7bdb395 100644 --- a/bookmarks/tests_e2e/e2e_test_settings_integrations.py +++ b/bookmarks/tests_e2e/e2e_test_settings_integrations.py @@ -1,67 +1,65 @@ from django.urls import reverse -from playwright.sync_api import sync_playwright, expect +from playwright.sync_api import expect from bookmarks.tests_e2e.helpers import LinkdingE2ETestCase class SettingsIntegrationsE2ETestCase(LinkdingE2ETestCase): def test_create_api_token(self): - with sync_playwright() as p: - self.open(reverse("linkding:settings.integrations"), p) + self.open(reverse("linkding:settings.integrations")) - # Click create API token button - self.page.get_by_text("Create API token").click() + # Click create API token button + self.page.get_by_text("Create API token").click() - # Wait for modal to appear - modal = self.page.locator(".modal") - expect(modal).to_be_visible() + # Wait for modal to appear + modal = self.page.locator(".modal") + expect(modal).to_be_visible() - # Enter custom token name - token_name_input = modal.locator("#token-name") - token_name_input.fill("") - token_name_input.fill("My Test Token") + # Enter custom token name + token_name_input = modal.locator("#token-name") + token_name_input.fill("") + token_name_input.fill("My Test Token") - # Confirm the dialog - modal.page.get_by_role("button", name="Create Token").click() + # Confirm the dialog + modal.page.get_by_role("button", name="Create Token").click() - # Verify the API token key is shown in the input - new_token_input = self.page.locator("#new-token-key") - expect(new_token_input).to_be_visible() - token_value = new_token_input.input_value() - self.assertTrue(len(token_value) > 0) + # Verify the API token key is shown in the input + new_token_input = self.page.locator("#new-token-key") + expect(new_token_input).to_be_visible() + token_value = new_token_input.input_value() + self.assertTrue(len(token_value) > 0) - # Verify the API token is now listed in the table - token_table = self.page.locator("table.crud-table") - expect(token_table).to_be_visible() - expect(token_table.get_by_text("My Test Token")).to_be_visible() + # Verify the API token is now listed in the table + token_table = self.page.locator("table.crud-table") + expect(token_table).to_be_visible() + expect(token_table.get_by_text("My Test Token")).to_be_visible() - # Verify the dialog is gone - expect(modal).to_be_hidden() + # Verify the dialog is gone + expect(modal).to_be_hidden() - # Reload the page to verify the API token key is only shown once - self.page.reload() + # Reload the page to verify the API token key is only shown once + self.page.reload() - # Token key input should no longer be visible - expect(new_token_input).not_to_be_visible() + # Token key input should no longer be visible + expect(new_token_input).not_to_be_visible() - # But the token should still be listed in the table - expect(token_table.get_by_text("My Test Token")).to_be_visible() + # But the token should still be listed in the table + expect(token_table.get_by_text("My Test Token")).to_be_visible() def test_delete_api_token(self): self.setup_api_token(name="Token To Delete") - with sync_playwright() as p: - self.open(reverse("linkding:settings.integrations"), p) + self.open(reverse("linkding:settings.integrations")) - token_table = self.page.locator("table.crud-table") - expect(token_table.get_by_text("Token To Delete")).to_be_visible() + token_table = self.page.locator("table.crud-table") + expect(token_table.get_by_text("Token To Delete")).to_be_visible() - # Click delete button for the token - token_row = token_table.locator("tr").filter(has_text="Token To Delete") - token_row.get_by_role("button", name="Delete").click() + # Click delete button for the token + token_row = token_table.locator("tr").filter(has_text="Token To Delete") + token_row.get_by_role("button", name="Delete").click() - # Confirm deletion - self.locate_confirm_dialog().get_by_text("Confirm").click() + # Confirm deletion + self.locate_confirm_dialog().get_by_text("Confirm").click() - # Verify the token is removed from the table - expect(token_table.get_by_text("Token To Delete")).not_to_be_visible() + # Verify the token is removed from the table + expect(token_table.get_by_text("Token To Delete")).not_to_be_visible() diff --git a/bookmarks/tests_e2e/e2e_test_tag_management.py b/bookmarks/tests_e2e/e2e_test_tag_management.py index 628fb8c..66899dd 100644 --- a/bookmarks/tests_e2e/e2e_test_tag_management.py +++ b/bookmarks/tests_e2e/e2e_test_tag_management.py @@ -1,5 +1,5 @@ from django.urls import reverse -from playwright.sync_api import sync_playwright, expect +from playwright.sync_api import expect from bookmarks.models import Tag from bookmarks.tests_e2e.helpers import LinkdingE2ETestCase @@ -25,30 +25,29 @@ class TagManagementE2ETestCase(LinkdingE2ETestCase): expect(success_message).to_contain_text(text) def test_create_tag(self): - with sync_playwright() as p: - self.open(reverse("linkding:tags.index"), p) + self.open(reverse("linkding:tags.index")) - # Click the Create Tag button to open the modal - self.page.get_by_text("Create Tag").click() + # Click the Create Tag button to open the modal + self.page.get_by_text("Create Tag").click() - modal = self.locate_tag_modal() + modal = self.locate_tag_modal() - # Fill in a tag name - name_input = modal.get_by_label("Name") - name_input.fill("test-tag") + # Fill in a tag name + name_input = modal.get_by_label("Name") + name_input.fill("test-tag") - # Submit the form - modal.get_by_text("Save").click() + # Submit the form + modal.get_by_text("Save").click() - # Verify modal is closed and we're back on the tags page - expect(modal).not_to_be_visible() + # Verify modal is closed and we're back on the tags page + expect(modal).not_to_be_visible() - # Verify the success message is shown - self.verify_success_message('Tag "test-tag" created successfully.') + # Verify the success message is shown + self.verify_success_message('Tag "test-tag" created successfully.') - # Verify the new tag is shown in the list - tag_row = self.locate_tag_row("test-tag") - expect(tag_row).to_be_visible() + # Verify the new tag is shown in the list + tag_row = self.locate_tag_row("test-tag") + expect(tag_row).to_be_visible() # Verify the tag was actually created in the database self.assertEqual( @@ -60,31 +59,30 @@ class TagManagementE2ETestCase(LinkdingE2ETestCase): def test_create_tag_validation_error(self): existing_tag = self.setup_tag(name="existing-tag") - with sync_playwright() as p: - self.open(reverse("linkding:tags.index"), p) + self.open(reverse("linkding:tags.index")) - # Click the Create Tag button to open the modal - self.page.get_by_text("Create Tag").click() + # Click the Create Tag button to open the modal + self.page.get_by_text("Create Tag").click() - modal = self.locate_tag_modal() + modal = self.locate_tag_modal() - # Submit with empty value - modal.get_by_text("Save").click() + # Submit with empty value + modal.get_by_text("Save").click() - # Verify the error is shown (field is required) - error_hint = modal.get_by_text("This field is required") - expect(error_hint).to_be_visible() + # Verify the error is shown (field is required) + error_hint = modal.get_by_text("This field is required") + expect(error_hint).to_be_visible() - # Fill in the name of an existing tag - name_input = modal.get_by_label("Name") - name_input.fill(existing_tag.name) + # Fill in the name of an existing tag + name_input = modal.get_by_label("Name") + name_input.fill(existing_tag.name) - # Submit the form - modal.get_by_text("Save").click() + # Submit the form + modal.get_by_text("Save").click() - # Verify the error is shown (tag already exists) - error_hint = modal.get_by_text('Tag "existing-tag" already exists') - expect(error_hint).to_be_visible() + # Verify the error is shown (tag already exists) + error_hint = modal.get_by_text('Tag "existing-tag" already exists') + expect(error_hint).to_be_visible() # Verify no additional tag was created self.assertEqual( @@ -94,34 +92,33 @@ class TagManagementE2ETestCase(LinkdingE2ETestCase): def test_edit_tag(self): tag = self.setup_tag(name="old-name") - with sync_playwright() as p: - self.open(reverse("linkding:tags.index"), p) + self.open(reverse("linkding:tags.index")) - # Click the Edit button for the tag - tag_row = self.locate_tag_row(tag.name) - tag_row.get_by_role("link", name="Edit").click() + # Click the Edit button for the tag + tag_row = self.locate_tag_row(tag.name) + tag_row.get_by_role("link", name="Edit").click() - modal = self.locate_tag_modal() + modal = self.locate_tag_modal() - # Verify the form is pre-filled with the tag name - name_input = modal.get_by_label("Name") - expect(name_input).to_have_value(tag.name) + # Verify the form is pre-filled with the tag name + name_input = modal.get_by_label("Name") + expect(name_input).to_have_value(tag.name) - # Change the tag name - name_input.fill("new-name") + # Change the tag name + name_input.fill("new-name") - # Submit the form - modal.get_by_text("Save").click() + # Submit the form + modal.get_by_text("Save").click() - # Verify modal is closed - expect(modal).not_to_be_visible() + # Verify modal is closed + expect(modal).not_to_be_visible() - # Verify the success message is shown - self.verify_success_message('Tag "new-name" updated successfully.') + # Verify the success message is shown + self.verify_success_message('Tag "new-name" updated successfully.') - # Verify the updated tag is shown in the list - expect(self.locate_tag_row("new-name")).to_be_visible() - expect(self.locate_tag_row("old-name")).not_to_be_visible() + # Verify the updated tag is shown in the list + expect(self.locate_tag_row("new-name")).to_be_visible() + expect(self.locate_tag_row("old-name")).not_to_be_visible() # Verify the tag was updated in the database tag.refresh_from_db() @@ -131,31 +128,30 @@ class TagManagementE2ETestCase(LinkdingE2ETestCase): tag = self.setup_tag(name="tag-to-edit") other_tag = self.setup_tag(name="other-tag") - with sync_playwright() as p: - self.open(reverse("linkding:tags.index"), p) + self.open(reverse("linkding:tags.index")) - # Click the Edit button for the tag - tag_row = self.locate_tag_row(tag.name) - tag_row.get_by_role("link", name="Edit").click() + # Click the Edit button for the tag + tag_row = self.locate_tag_row(tag.name) + tag_row.get_by_role("link", name="Edit").click() - modal = self.locate_tag_modal() + modal = self.locate_tag_modal() - # Clear the name and submit - name_input = modal.get_by_label("Name") - name_input.fill("") - modal.get_by_text("Save").click() + # Clear the name and submit + name_input = modal.get_by_label("Name") + name_input.fill("") + modal.get_by_text("Save").click() - # Verify the error is shown (field is required) - error_hint = modal.get_by_text("This field is required") - expect(error_hint).to_be_visible() + # Verify the error is shown (field is required) + error_hint = modal.get_by_text("This field is required") + expect(error_hint).to_be_visible() - # Fill in the name of another existing tag - name_input.fill(other_tag.name) - modal.get_by_text("Save").click() + # Fill in the name of another existing tag + name_input.fill(other_tag.name) + modal.get_by_text("Save").click() - # Verify the error is shown (tag already exists) - error_hint = modal.get_by_text('Tag "other-tag" already exists') - expect(error_hint).to_be_visible() + # Verify the error is shown (tag already exists) + error_hint = modal.get_by_text('Tag "other-tag" already exists') + expect(error_hint).to_be_visible() # Verify the tag was not modified tag.refresh_from_db() @@ -170,37 +166,36 @@ class TagManagementE2ETestCase(LinkdingE2ETestCase): bookmark1 = self.setup_bookmark(tags=[merge_tag1]) bookmark2 = self.setup_bookmark(tags=[merge_tag2]) - with sync_playwright() as p: - self.open(reverse("linkding:tags.index"), p) + self.open(reverse("linkding:tags.index")) - # Click the Merge Tags button to open the modal - self.page.get_by_text("Merge Tags", exact=True).click() + # Click the Merge Tags button to open the modal + self.page.get_by_text("Merge Tags", exact=True).click() - modal = self.locate_merge_modal() + modal = self.locate_merge_modal() - # Fill in the target tag - target_input = modal.get_by_label("Target tag") - target_input.fill(target_tag.name) + # Fill in the target tag + target_input = modal.get_by_label("Target tag") + target_input.fill(target_tag.name) - # Fill in the tags to merge - merge_input = modal.get_by_label("Tags to merge") - merge_input.fill(f"{merge_tag1.name} {merge_tag2.name}") + # Fill in the tags to merge + merge_input = modal.get_by_label("Tags to merge") + merge_input.fill(f"{merge_tag1.name} {merge_tag2.name}") - # Submit the form - modal.get_by_role("button", name="Merge Tags").click() + # Submit the form + modal.get_by_role("button", name="Merge Tags").click() - # Verify modal is closed - expect(modal).not_to_be_visible() + # Verify modal is closed + expect(modal).not_to_be_visible() - # Verify the success message is shown - self.verify_success_message( - 'Successfully merged 2 tags (merge-tag1, merge-tag2) into "target-tag".' - ) + # Verify the success message is shown + self.verify_success_message( + 'Successfully merged 2 tags (merge-tag1, merge-tag2) into "target-tag".' + ) - # Verify the merged tags are no longer in the list - expect(self.locate_tag_row("target-tag")).to_be_visible() - expect(self.locate_tag_row("merge-tag1")).not_to_be_visible() - expect(self.locate_tag_row("merge-tag2")).not_to_be_visible() + # Verify the merged tags are no longer in the list + expect(self.locate_tag_row("target-tag")).to_be_visible() + expect(self.locate_tag_row("merge-tag1")).not_to_be_visible() + expect(self.locate_tag_row("merge-tag2")).not_to_be_visible() # Verify the merge tags were deleted self.assertEqual( @@ -217,44 +212,43 @@ class TagManagementE2ETestCase(LinkdingE2ETestCase): target_tag = self.setup_tag(name="target-tag") merge_tag = self.setup_tag(name="merge-tag") - with sync_playwright() as p: - self.open(reverse("linkding:tags.index"), p) + self.open(reverse("linkding:tags.index")) - # Click the Merge Tags button to open the modal - self.page.get_by_text("Merge Tags", exact=True).click() + # Click the Merge Tags button to open the modal + self.page.get_by_text("Merge Tags", exact=True).click() - modal = self.locate_merge_modal() + modal = self.locate_merge_modal() - # Submit with empty values - modal.get_by_role("button", name="Merge Tags").click() + # Submit with empty values + modal.get_by_role("button", name="Merge Tags").click() - # Verify the errors are shown - expect(modal.get_by_text("This field is required").first).to_be_visible() + # Verify the errors are shown + expect(modal.get_by_text("This field is required").first).to_be_visible() - # Fill in non-existent target tag - target_input = modal.get_by_label("Target tag") - target_input.fill("nonexistent-tag") + # Fill in non-existent target tag + target_input = modal.get_by_label("Target tag") + target_input.fill("nonexistent-tag") - merge_input = modal.get_by_label("Tags to merge") - merge_input.fill(merge_tag.name) + merge_input = modal.get_by_label("Tags to merge") + merge_input.fill(merge_tag.name) - modal.get_by_role("button", name="Merge Tags").click() + modal.get_by_role("button", name="Merge Tags").click() - # Verify error for non-existent target tag - expect( - modal.get_by_text('Tag "nonexistent-tag" does not exist') - ).to_be_visible() + # Verify error for non-existent target tag + expect( + modal.get_by_text('Tag "nonexistent-tag" does not exist') + ).to_be_visible() - # Fill in valid target but target tag in merge tags - target_input.fill(target_tag.name) - merge_input.fill(target_tag.name) + # Fill in valid target but target tag in merge tags + target_input.fill(target_tag.name) + merge_input.fill(target_tag.name) - modal.get_by_role("button", name="Merge Tags").click() + modal.get_by_role("button", name="Merge Tags").click() - # Verify error for target tag in merge tags - expect( - modal.get_by_text("The target tag cannot be selected for merging") - ).to_be_visible() + # Verify error for target tag in merge tags + expect( + modal.get_by_text("The target tag cannot be selected for merging") + ).to_be_visible() # Verify no tags were deleted self.assertEqual( diff --git a/bookmarks/tests_e2e/helpers.py b/bookmarks/tests_e2e/helpers.py index ae5c89a..ff2e7eb 100644 --- a/bookmarks/tests_e2e/helpers.py +++ b/bookmarks/tests_e2e/helpers.py @@ -1,18 +1,66 @@ +import os + from django.contrib.staticfiles.testing import LiveServerTestCase -from playwright.sync_api import BrowserContext, Playwright, Page +from playwright.sync_api import BrowserContext, Page, sync_playwright from playwright.sync_api import expect from bookmarks.tests.helpers import BookmarkFactoryMixin +SCREENSHOT_DIR = "test-results/screenshots" + +# Allow Django ORM operations within Playwright's async context +os.environ.setdefault("DJANGO_ALLOW_ASYNC_UNSAFE", "true") + class LinkdingE2ETestCase(LiveServerTestCase, BookmarkFactoryMixin): def setUp(self) -> None: self.client.force_login(self.get_or_create_test_user()) self.cookie = self.client.cookies["sessionid"] + self.playwright = None + self.browser = None + self.context = None + self.page = None - def setup_browser(self, playwright) -> BrowserContext: - browser = playwright.chromium.launch(headless=True) - context = browser.new_context() + def tearDown(self) -> None: + if self.page and self._test_has_failed(): + self._capture_screenshot() + if self.browser: + self.browser.close() + if self.playwright: + self.playwright.stop() + super().tearDown() + + def _test_has_failed(self) -> bool: + """Detect if the current test has failed. Works with both Django/unittest and pytest.""" + # Check _outcome for failure info + if self._outcome is not None: + result = self._outcome.result + if result: + # pytest stores exception info in _excinfo + if hasattr(result, "_excinfo") and result._excinfo: + return True + # Django/unittest stores failures and errors in the result + # Check if THIS test is in failures/errors (not just any test) + if hasattr(result, "failures"): + for failed_test, _ in result.failures: + if failed_test is self: + return True + return False + + def _ensure_playwright(self): + if not self.playwright: + self.playwright = sync_playwright().start() + self.browser = self.playwright.chromium.launch(headless=True) + + def _capture_screenshot(self): + os.makedirs(SCREENSHOT_DIR, exist_ok=True) + filename = f"{self.__class__.__name__}_{self._testMethodName}.png" + filepath = os.path.join(SCREENSHOT_DIR, filename) + self.page.screenshot(path=filepath, full_page=True) + + def setup_browser(self) -> BrowserContext: + self._ensure_playwright() + context = self.browser.new_context() context.add_cookies( [ { @@ -25,14 +73,20 @@ class LinkdingE2ETestCase(LiveServerTestCase, BookmarkFactoryMixin): ) return context - def open(self, url: str, playwright: Playwright) -> Page: - browser = self.setup_browser(playwright) - self.page = browser.new_page() + def open(self, url: str) -> Page: + self.context = self.setup_browser() + self.page = self.context.new_page() + self.page.on("pageerror", self.on_page_error) self.page.goto(self.live_server_url + url) self.page.on("load", self.on_load) self.num_loads = 0 return self.page + def on_page_error(self, error): + print(f"[JS ERROR] {error}") + if hasattr(error, "stack"): + print(f"[JS STACK] {error.stack}") + def on_load(self): self.num_loads += 1 diff --git a/scripts/setup-oicd.sh b/scripts/setup-oicd.sh deleted file mode 100644 index c91b1bd..0000000 --- a/scripts/setup-oicd.sh +++ /dev/null @@ -1,8 +0,0 @@ -# Example setup for OIDC with Zitadel -export LD_ENABLE_OIDC=True -export OIDC_USE_PKCE=True -export OIDC_OP_AUTHORIZATION_ENDPOINT=http://localhost:8080/oauth/v2/authorize -export OIDC_OP_TOKEN_ENDPOINT=http://localhost:8080/oauth/v2/token -export OIDC_OP_USER_ENDPOINT=http://localhost:8080/oidc/v1/userinfo -export OIDC_OP_JWKS_ENDPOINT=http://localhost:8080/oauth/v2/keys -export OIDC_RP_CLIENT_ID= diff --git a/scripts/test-e2e.sh b/scripts/test-e2e.sh deleted file mode 100755 index 7da6892..0000000 --- a/scripts/test-e2e.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env bash - -# Make sure Chromium is installed -uv run playwright install chromium - -# Test server loads assets from static folder, so make sure files there are up-to-date -rm -rf static -npm run build -uv run manage.py collectstatic - -# Run E2E tests -uv run manage.py test bookmarks.tests_e2e --pattern="e2e_test_*.py" diff --git a/scripts/test-postgres.sh b/scripts/test-postgres.sh index 660c43c..b768523 100755 --- a/scripts/test-postgres.sh +++ b/scripts/test-postgres.sh @@ -23,7 +23,8 @@ export LD_DB_ENGINE=postgres export LD_DB_USER=linkding export LD_DB_PASSWORD=linkding -./scripts/test.sh +make test +make e2e # Remove postgres container docker rm -f linkding-postgres-test || true diff --git a/scripts/test-unit.sh b/scripts/test-unit.sh deleted file mode 100755 index 84dae2a..0000000 --- a/scripts/test-unit.sh +++ /dev/null @@ -1 +0,0 @@ -uv run manage.py test bookmarks.tests diff --git a/scripts/test.sh b/scripts/test.sh deleted file mode 100755 index 4750ce5..0000000 --- a/scripts/test.sh +++ /dev/null @@ -1,2 +0,0 @@ -./scripts/test-unit.sh -./scripts/test-e2e.sh