mirror of
https://github.com/sissbruecker/linkding.git
synced 2026-02-27 22:43:15 +08:00
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
This commit is contained in:
10
.github/workflows/main.yaml
vendored
10
.github/workflows/main.yaml
vendored
@@ -30,7 +30,7 @@ jobs:
|
|||||||
uv sync
|
uv sync
|
||||||
mkdir data
|
mkdir data
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: uv run manage.py test bookmarks.tests
|
run: uv run pytest -n auto
|
||||||
e2e_tests:
|
e2e_tests:
|
||||||
name: E2E Tests
|
name: E2E Tests
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -59,4 +59,10 @@ jobs:
|
|||||||
npm run build
|
npm run build
|
||||||
uv run manage.py collectstatic
|
uv run manage.py collectstatic
|
||||||
- name: Run tests
|
- 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
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -60,6 +60,7 @@ coverage.xml
|
|||||||
*.cover
|
*.cover
|
||||||
.hypothesis/
|
.hypothesis/
|
||||||
.pytest_cache/
|
.pytest_cache/
|
||||||
|
test-results/
|
||||||
|
|
||||||
# Translations
|
# Translations
|
||||||
*.mo
|
*.mo
|
||||||
|
|||||||
@@ -1,73 +1,69 @@
|
|||||||
from django.urls import reverse
|
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.tests_e2e.helpers import LinkdingE2ETestCase
|
||||||
|
|
||||||
|
|
||||||
class A11yNavigationFocusTest(LinkdingE2ETestCase):
|
class A11yNavigationFocusTest(LinkdingE2ETestCase):
|
||||||
def test_initial_page_load_focus(self):
|
def test_initial_page_load_focus(self):
|
||||||
with sync_playwright() as p:
|
# First page load should keep focus on the body
|
||||||
# First page load should keep focus on the body
|
page = self.open(reverse("linkding:bookmarks.index"))
|
||||||
page = self.open(reverse("linkding:bookmarks.index"), p)
|
focused_tag = page.evaluate("document.activeElement?.tagName")
|
||||||
focused_tag = page.evaluate("document.activeElement?.tagName")
|
self.assertEqual("BODY", focused_tag)
|
||||||
self.assertEqual("BODY", focused_tag)
|
|
||||||
|
|
||||||
page.goto(self.live_server_url + reverse("linkding:bookmarks.archived"))
|
page.goto(self.live_server_url + reverse("linkding:bookmarks.archived"))
|
||||||
focused_tag = page.evaluate("document.activeElement?.tagName")
|
focused_tag = page.evaluate("document.activeElement?.tagName")
|
||||||
self.assertEqual("BODY", focused_tag)
|
self.assertEqual("BODY", focused_tag)
|
||||||
|
|
||||||
page.goto(self.live_server_url + reverse("linkding:settings.general"))
|
page.goto(self.live_server_url + reverse("linkding:settings.general"))
|
||||||
focused_tag = page.evaluate("document.activeElement?.tagName")
|
focused_tag = page.evaluate("document.activeElement?.tagName")
|
||||||
self.assertEqual("BODY", focused_tag)
|
self.assertEqual("BODY", focused_tag)
|
||||||
|
|
||||||
# Bookmark form views should focus the URL input
|
# Bookmark form views should focus the URL input
|
||||||
page.goto(self.live_server_url + reverse("linkding:bookmarks.new"))
|
page.goto(self.live_server_url + reverse("linkding:bookmarks.new"))
|
||||||
page.wait_for_timeout(timeout=1000)
|
page.wait_for_timeout(timeout=1000)
|
||||||
focused_tag = page.evaluate(
|
focused_tag = page.evaluate(
|
||||||
"document.activeElement?.tagName + '|' + document.activeElement?.name"
|
"document.activeElement?.tagName + '|' + document.activeElement?.name"
|
||||||
)
|
)
|
||||||
self.assertEqual("INPUT|url", focused_tag)
|
self.assertEqual("INPUT|url", focused_tag)
|
||||||
|
|
||||||
def test_page_navigation_focus(self):
|
def test_page_navigation_focus(self):
|
||||||
bookmark = self.setup_bookmark()
|
bookmark = self.setup_bookmark()
|
||||||
|
|
||||||
with sync_playwright() as p:
|
page = self.open(reverse("linkding:bookmarks.index"))
|
||||||
page = self.open(reverse("linkding:bookmarks.index"), p)
|
|
||||||
|
|
||||||
# Subsequent navigation should move focus to main content
|
# Subsequent navigation should move focus to main content
|
||||||
self.reset_focus()
|
self.reset_focus()
|
||||||
self.navigate_menu("Bookmarks", "Active")
|
self.navigate_menu("Bookmarks", "Active")
|
||||||
focused = page.locator("main:focus")
|
focused = page.locator("main:focus")
|
||||||
expect(focused).to_be_visible()
|
expect(focused).to_be_visible()
|
||||||
|
|
||||||
self.reset_focus()
|
self.reset_focus()
|
||||||
self.navigate_menu("Bookmarks", "Archived")
|
self.navigate_menu("Bookmarks", "Archived")
|
||||||
focused = page.locator("main:focus")
|
focused = page.locator("main:focus")
|
||||||
expect(focused).to_be_visible()
|
expect(focused).to_be_visible()
|
||||||
|
|
||||||
self.reset_focus()
|
self.reset_focus()
|
||||||
self.navigate_menu("Settings", "General")
|
self.navigate_menu("Settings", "General")
|
||||||
focused = page.locator("main:focus")
|
focused = page.locator("main:focus")
|
||||||
expect(focused).to_be_visible()
|
expect(focused).to_be_visible()
|
||||||
|
|
||||||
# Bookmark form views should focus the URL input
|
# Bookmark form views should focus the URL input
|
||||||
self.reset_focus()
|
self.reset_focus()
|
||||||
self.navigate_menu("Add bookmark")
|
self.navigate_menu("Add bookmark")
|
||||||
focused = page.locator("input[name='url']:focus")
|
focused = page.locator("input[name='url']:focus")
|
||||||
expect(focused).to_be_visible()
|
expect(focused).to_be_visible()
|
||||||
|
|
||||||
# Opening details modal should move focus to close button
|
# Opening details modal should move focus to close button
|
||||||
self.navigate_menu("Bookmarks", "Active")
|
self.navigate_menu("Bookmarks", "Active")
|
||||||
self.open_details_modal(bookmark)
|
self.open_details_modal(bookmark)
|
||||||
focused = page.locator(".modal button.close:focus")
|
focused = page.locator(".modal button.close:focus")
|
||||||
expect(focused).to_be_visible()
|
expect(focused).to_be_visible()
|
||||||
|
|
||||||
# Closing modal should move focus back to the bookmark item
|
# Closing modal should move focus back to the bookmark item
|
||||||
page.keyboard.press("Escape")
|
page.keyboard.press("Escape")
|
||||||
focused = self.locate_bookmark(bookmark.title).locator(
|
focused = self.locate_bookmark(bookmark.title).locator("a.view-action:focus")
|
||||||
"a.view-action:focus"
|
expect(focused).to_be_visible()
|
||||||
)
|
|
||||||
expect(focused).to_be_visible()
|
|
||||||
|
|
||||||
def reset_focus(self):
|
def reset_focus(self):
|
||||||
self.page.evaluate("document.activeElement.blur()")
|
self.page.evaluate("document.activeElement.blur()")
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from django.test import override_settings
|
from django.test import override_settings
|
||||||
from django.urls import reverse
|
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.tests_e2e.helpers import LinkdingE2ETestCase
|
||||||
from bookmarks.models import Bookmark
|
from bookmarks.models import Bookmark
|
||||||
@@ -10,78 +10,74 @@ class BookmarkDetailsModalE2ETestCase(LinkdingE2ETestCase):
|
|||||||
def test_show_details(self):
|
def test_show_details(self):
|
||||||
bookmark = self.setup_bookmark()
|
bookmark = self.setup_bookmark()
|
||||||
|
|
||||||
with sync_playwright() as p:
|
self.open(reverse("linkding:bookmarks.index"))
|
||||||
self.open(reverse("linkding:bookmarks.index"), p)
|
|
||||||
|
|
||||||
details_modal = self.open_details_modal(bookmark)
|
details_modal = self.open_details_modal(bookmark)
|
||||||
title = details_modal.locator("h2")
|
title = details_modal.locator("h2")
|
||||||
expect(title).to_have_text(bookmark.title)
|
expect(title).to_have_text(bookmark.title)
|
||||||
|
|
||||||
def test_close_details(self):
|
def test_close_details(self):
|
||||||
bookmark = self.setup_bookmark()
|
bookmark = self.setup_bookmark()
|
||||||
|
|
||||||
with sync_playwright() as p:
|
self.open(reverse("linkding:bookmarks.index"))
|
||||||
self.open(reverse("linkding:bookmarks.index"), p)
|
|
||||||
|
|
||||||
# close with close button
|
# close with close button
|
||||||
details_modal = self.open_details_modal(bookmark)
|
details_modal = self.open_details_modal(bookmark)
|
||||||
details_modal.locator("button.close").click()
|
details_modal.locator("button.close").click()
|
||||||
expect(details_modal).to_be_hidden()
|
expect(details_modal).to_be_hidden()
|
||||||
|
|
||||||
# close with backdrop
|
# close with backdrop
|
||||||
details_modal = self.open_details_modal(bookmark)
|
details_modal = self.open_details_modal(bookmark)
|
||||||
overlay = details_modal.locator(".modal-overlay")
|
overlay = details_modal.locator(".modal-overlay")
|
||||||
overlay.click(position={"x": 0, "y": 0})
|
overlay.click(position={"x": 0, "y": 0})
|
||||||
expect(details_modal).to_be_hidden()
|
expect(details_modal).to_be_hidden()
|
||||||
|
|
||||||
# close with escape
|
# close with escape
|
||||||
details_modal = self.open_details_modal(bookmark)
|
details_modal = self.open_details_modal(bookmark)
|
||||||
self.page.keyboard.press("Escape")
|
self.page.keyboard.press("Escape")
|
||||||
expect(details_modal).to_be_hidden()
|
expect(details_modal).to_be_hidden()
|
||||||
|
|
||||||
def test_toggle_archived(self):
|
def test_toggle_archived(self):
|
||||||
bookmark = self.setup_bookmark()
|
bookmark = self.setup_bookmark()
|
||||||
|
|
||||||
with sync_playwright() as p:
|
# archive
|
||||||
# archive
|
url = reverse("linkding:bookmarks.index")
|
||||||
url = reverse("linkding:bookmarks.index")
|
self.open(url)
|
||||||
self.open(url, p)
|
|
||||||
|
|
||||||
details_modal = self.open_details_modal(bookmark)
|
details_modal = self.open_details_modal(bookmark)
|
||||||
details_modal.get_by_text("Archived", exact=False).click()
|
details_modal.get_by_text("Archived", exact=False).click()
|
||||||
expect(self.locate_bookmark(bookmark.title)).not_to_be_visible()
|
expect(self.locate_bookmark(bookmark.title)).not_to_be_visible()
|
||||||
self.assertReloads(0)
|
self.assertReloads(0)
|
||||||
|
|
||||||
# unarchive
|
# unarchive
|
||||||
url = reverse("linkding:bookmarks.archived")
|
url = reverse("linkding:bookmarks.archived")
|
||||||
self.page.goto(self.live_server_url + url)
|
self.page.goto(self.live_server_url + url)
|
||||||
self.resetReloads()
|
self.resetReloads()
|
||||||
|
|
||||||
details_modal = self.open_details_modal(bookmark)
|
details_modal = self.open_details_modal(bookmark)
|
||||||
details_modal.get_by_text("Archived", exact=False).click()
|
details_modal.get_by_text("Archived", exact=False).click()
|
||||||
expect(self.locate_bookmark(bookmark.title)).not_to_be_visible()
|
expect(self.locate_bookmark(bookmark.title)).not_to_be_visible()
|
||||||
self.assertReloads(0)
|
self.assertReloads(0)
|
||||||
|
|
||||||
def test_toggle_unread(self):
|
def test_toggle_unread(self):
|
||||||
bookmark = self.setup_bookmark()
|
bookmark = self.setup_bookmark()
|
||||||
|
|
||||||
with sync_playwright() as p:
|
# mark as unread
|
||||||
# mark as unread
|
url = reverse("linkding:bookmarks.index")
|
||||||
url = reverse("linkding:bookmarks.index")
|
self.open(url)
|
||||||
self.open(url, p)
|
|
||||||
|
|
||||||
details_modal = self.open_details_modal(bookmark)
|
details_modal = self.open_details_modal(bookmark)
|
||||||
|
|
||||||
details_modal.get_by_text("Unread").click()
|
details_modal.get_by_text("Unread").click()
|
||||||
bookmark_item = self.locate_bookmark(bookmark.title)
|
bookmark_item = self.locate_bookmark(bookmark.title)
|
||||||
expect(bookmark_item.get_by_text("Unread")).to_be_visible()
|
expect(bookmark_item.get_by_text("Unread")).to_be_visible()
|
||||||
self.assertReloads(0)
|
self.assertReloads(0)
|
||||||
|
|
||||||
# mark as read
|
# mark as read
|
||||||
details_modal.get_by_text("Unread").click()
|
details_modal.get_by_text("Unread").click()
|
||||||
bookmark_item = self.locate_bookmark(bookmark.title)
|
bookmark_item = self.locate_bookmark(bookmark.title)
|
||||||
expect(bookmark_item.get_by_text("Unread")).not_to_be_visible()
|
expect(bookmark_item.get_by_text("Unread")).not_to_be_visible()
|
||||||
self.assertReloads(0)
|
self.assertReloads(0)
|
||||||
|
|
||||||
def test_toggle_shared(self):
|
def test_toggle_shared(self):
|
||||||
profile = self.get_or_create_test_user().profile
|
profile = self.get_or_create_test_user().profile
|
||||||
@@ -90,61 +86,58 @@ class BookmarkDetailsModalE2ETestCase(LinkdingE2ETestCase):
|
|||||||
|
|
||||||
bookmark = self.setup_bookmark()
|
bookmark = self.setup_bookmark()
|
||||||
|
|
||||||
with sync_playwright() as p:
|
# share bookmark
|
||||||
# share bookmark
|
url = reverse("linkding:bookmarks.index")
|
||||||
url = reverse("linkding:bookmarks.index")
|
self.open(url)
|
||||||
self.open(url, p)
|
|
||||||
|
|
||||||
details_modal = self.open_details_modal(bookmark)
|
details_modal = self.open_details_modal(bookmark)
|
||||||
|
|
||||||
details_modal.get_by_text("Shared").click()
|
details_modal.get_by_text("Shared").click()
|
||||||
bookmark_item = self.locate_bookmark(bookmark.title)
|
bookmark_item = self.locate_bookmark(bookmark.title)
|
||||||
expect(bookmark_item.get_by_text("Shared")).to_be_visible()
|
expect(bookmark_item.get_by_text("Shared")).to_be_visible()
|
||||||
self.assertReloads(0)
|
self.assertReloads(0)
|
||||||
|
|
||||||
# unshare bookmark
|
# unshare bookmark
|
||||||
details_modal.get_by_text("Shared").click()
|
details_modal.get_by_text("Shared").click()
|
||||||
bookmark_item = self.locate_bookmark(bookmark.title)
|
bookmark_item = self.locate_bookmark(bookmark.title)
|
||||||
expect(bookmark_item.get_by_text("Shared")).not_to_be_visible()
|
expect(bookmark_item.get_by_text("Shared")).not_to_be_visible()
|
||||||
self.assertReloads(0)
|
self.assertReloads(0)
|
||||||
|
|
||||||
def test_edit_return_url(self):
|
def test_edit_return_url(self):
|
||||||
bookmark = self.setup_bookmark()
|
bookmark = self.setup_bookmark()
|
||||||
|
|
||||||
with sync_playwright() as p:
|
url = reverse("linkding:bookmarks.index") + f"?q={bookmark.title}"
|
||||||
url = reverse("linkding:bookmarks.index") + f"?q={bookmark.title}"
|
self.open(url)
|
||||||
self.open(url, p)
|
|
||||||
|
|
||||||
details_modal = self.open_details_modal(bookmark)
|
details_modal = self.open_details_modal(bookmark)
|
||||||
|
|
||||||
# Navigate to edit page
|
# Navigate to edit page
|
||||||
with self.page.expect_navigation():
|
with self.page.expect_navigation():
|
||||||
details_modal.get_by_text("Edit").click()
|
details_modal.get_by_text("Edit").click()
|
||||||
|
|
||||||
# Cancel edit, verify return to details url
|
# Cancel edit, verify return to details url
|
||||||
details_url = url + f"&details={bookmark.id}"
|
details_url = url + f"&details={bookmark.id}"
|
||||||
with self.page.expect_navigation(url=self.live_server_url + details_url):
|
with self.page.expect_navigation(url=self.live_server_url + details_url):
|
||||||
self.page.get_by_text("Cancel").click()
|
self.page.get_by_text("Cancel").click()
|
||||||
|
|
||||||
def test_delete(self):
|
def test_delete(self):
|
||||||
bookmark = self.setup_bookmark()
|
bookmark = self.setup_bookmark()
|
||||||
|
|
||||||
with sync_playwright() as p:
|
url = reverse("linkding:bookmarks.index") + f"?q={bookmark.title}"
|
||||||
url = reverse("linkding:bookmarks.index") + f"?q={bookmark.title}"
|
self.open(url)
|
||||||
self.open(url, p)
|
|
||||||
|
|
||||||
details_modal = self.open_details_modal(bookmark)
|
details_modal = self.open_details_modal(bookmark)
|
||||||
|
|
||||||
# Wait for confirm button to be initialized
|
# Wait for confirm button to be initialized
|
||||||
self.page.wait_for_timeout(1000)
|
self.page.wait_for_timeout(1000)
|
||||||
|
|
||||||
# Delete bookmark, verify return url
|
# Delete bookmark, verify return url
|
||||||
with self.page.expect_navigation(url=self.live_server_url + url):
|
with self.page.expect_navigation(url=self.live_server_url + url):
|
||||||
details_modal.get_by_text("Delete").click()
|
details_modal.get_by_text("Delete").click()
|
||||||
self.locate_confirm_dialog().get_by_text("Confirm").click()
|
self.locate_confirm_dialog().get_by_text("Confirm").click()
|
||||||
|
|
||||||
# verify bookmark is deleted
|
# verify bookmark is deleted
|
||||||
expect(self.locate_bookmark(bookmark.title)).not_to_be_visible()
|
expect(self.locate_bookmark(bookmark.title)).not_to_be_visible()
|
||||||
|
|
||||||
self.assertEqual(Bookmark.objects.count(), 0)
|
self.assertEqual(Bookmark.objects.count(), 0)
|
||||||
|
|
||||||
@@ -152,28 +145,27 @@ class BookmarkDetailsModalE2ETestCase(LinkdingE2ETestCase):
|
|||||||
def test_create_snapshot_remove_snapshot(self):
|
def test_create_snapshot_remove_snapshot(self):
|
||||||
bookmark = self.setup_bookmark()
|
bookmark = self.setup_bookmark()
|
||||||
|
|
||||||
with sync_playwright() as p:
|
url = reverse("linkding:bookmarks.index") + f"?q={bookmark.title}"
|
||||||
url = reverse("linkding:bookmarks.index") + f"?q={bookmark.title}"
|
self.open(url)
|
||||||
self.open(url, p)
|
|
||||||
|
|
||||||
details_modal = self.open_details_modal(bookmark)
|
details_modal = self.open_details_modal(bookmark)
|
||||||
asset_list = details_modal.locator(".assets")
|
asset_list = details_modal.locator(".assets")
|
||||||
|
|
||||||
# No snapshots initially
|
# No snapshots initially
|
||||||
snapshot = asset_list.get_by_text("HTML snapshot from", exact=False)
|
snapshot = asset_list.get_by_text("HTML snapshot from", exact=False)
|
||||||
expect(snapshot).not_to_be_visible()
|
expect(snapshot).not_to_be_visible()
|
||||||
|
|
||||||
# Create snapshot
|
# Create snapshot
|
||||||
details_modal.get_by_text("Create HTML snapshot", exact=False).click()
|
details_modal.get_by_text("Create HTML snapshot", exact=False).click()
|
||||||
self.assertReloads(0)
|
self.assertReloads(0)
|
||||||
|
|
||||||
# Has new snapshots
|
# Has new snapshots
|
||||||
expect(snapshot).to_be_visible()
|
expect(snapshot).to_be_visible()
|
||||||
|
|
||||||
# Remove snapshot
|
# Remove snapshot
|
||||||
asset_list.get_by_text("Remove", exact=False).click()
|
asset_list.get_by_text("Remove", exact=False).click()
|
||||||
self.locate_confirm_dialog().get_by_text("Confirm", exact=False).click()
|
self.locate_confirm_dialog().get_by_text("Confirm", exact=False).click()
|
||||||
|
|
||||||
# Snapshot is removed
|
# Snapshot is removed
|
||||||
expect(snapshot).not_to_be_visible()
|
expect(snapshot).not_to_be_visible()
|
||||||
self.assertReloads(0)
|
self.assertReloads(0)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from unittest import skip
|
from unittest import skip
|
||||||
|
|
||||||
from django.urls import reverse
|
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.tests_e2e.helpers import LinkdingE2ETestCase
|
||||||
|
|
||||||
@@ -11,15 +11,14 @@ class BookmarkItemE2ETestCase(LinkdingE2ETestCase):
|
|||||||
def test_toggle_notes_should_show_hide_notes(self):
|
def test_toggle_notes_should_show_hide_notes(self):
|
||||||
bookmark = self.setup_bookmark(notes="Test notes")
|
bookmark = self.setup_bookmark(notes="Test notes")
|
||||||
|
|
||||||
with sync_playwright() as p:
|
page = self.open(reverse("linkding:bookmarks.index"))
|
||||||
page = self.open(reverse("linkding:bookmarks.index"), p)
|
|
||||||
|
|
||||||
notes = self.locate_bookmark(bookmark.title).locator(".notes")
|
notes = self.locate_bookmark(bookmark.title).locator(".notes")
|
||||||
expect(notes).to_be_hidden()
|
expect(notes).to_be_hidden()
|
||||||
|
|
||||||
toggle_notes = page.locator("li button.toggle-notes")
|
toggle_notes = page.locator("li button.toggle-notes")
|
||||||
toggle_notes.click()
|
toggle_notes.click()
|
||||||
expect(notes).to_be_visible()
|
expect(notes).to_be_visible()
|
||||||
|
|
||||||
toggle_notes.click()
|
toggle_notes.click()
|
||||||
expect(notes).to_be_hidden()
|
expect(notes).to_be_hidden()
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from django.urls import reverse
|
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.tests_e2e.helpers import LinkdingE2ETestCase
|
||||||
from bookmarks.models import Bookmark
|
from bookmarks.models import Bookmark
|
||||||
@@ -36,19 +36,18 @@ class BookmarkPageBulkEditE2ETestCase(LinkdingE2ETestCase):
|
|||||||
def test_active_bookmarks_bulk_select_across(self):
|
def test_active_bookmarks_bulk_select_across(self):
|
||||||
self.setup_test_data()
|
self.setup_test_data()
|
||||||
|
|
||||||
with sync_playwright() as p:
|
self.open(reverse("linkding:bookmarks.index"))
|
||||||
self.open(reverse("linkding:bookmarks.index"), p)
|
|
||||||
|
|
||||||
bookmark_list = self.locate_bookmark_list().element_handle()
|
bookmark_list = self.locate_bookmark_list().element_handle()
|
||||||
self.locate_bulk_edit_toggle().click()
|
self.locate_bulk_edit_toggle().click()
|
||||||
self.locate_bulk_edit_select_all().click()
|
self.locate_bulk_edit_select_all().click()
|
||||||
self.locate_bulk_edit_select_across().click()
|
self.locate_bulk_edit_select_across().click()
|
||||||
|
|
||||||
self.select_bulk_action("Delete")
|
self.select_bulk_action("Delete")
|
||||||
self.locate_bulk_edit_bar().get_by_text("Execute").click()
|
self.locate_bulk_edit_bar().get_by_text("Execute").click()
|
||||||
self.locate_confirm_dialog().get_by_text("Confirm").click()
|
self.locate_confirm_dialog().get_by_text("Confirm").click()
|
||||||
# Wait until bookmark list is updated (old reference becomes invisible)
|
# Wait until bookmark list is updated (old reference becomes invisible)
|
||||||
bookmark_list.wait_for_element_state("hidden", timeout=1000)
|
bookmark_list.wait_for_element_state("hidden", timeout=1000)
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
0,
|
0,
|
||||||
@@ -74,19 +73,18 @@ class BookmarkPageBulkEditE2ETestCase(LinkdingE2ETestCase):
|
|||||||
def test_archived_bookmarks_bulk_select_across(self):
|
def test_archived_bookmarks_bulk_select_across(self):
|
||||||
self.setup_test_data()
|
self.setup_test_data()
|
||||||
|
|
||||||
with sync_playwright() as p:
|
self.open(reverse("linkding:bookmarks.archived"))
|
||||||
self.open(reverse("linkding:bookmarks.archived"), p)
|
|
||||||
|
|
||||||
bookmark_list = self.locate_bookmark_list().element_handle()
|
bookmark_list = self.locate_bookmark_list().element_handle()
|
||||||
self.locate_bulk_edit_toggle().click()
|
self.locate_bulk_edit_toggle().click()
|
||||||
self.locate_bulk_edit_select_all().click()
|
self.locate_bulk_edit_select_all().click()
|
||||||
self.locate_bulk_edit_select_across().click()
|
self.locate_bulk_edit_select_across().click()
|
||||||
|
|
||||||
self.select_bulk_action("Delete")
|
self.select_bulk_action("Delete")
|
||||||
self.locate_bulk_edit_bar().get_by_text("Execute").click()
|
self.locate_bulk_edit_bar().get_by_text("Execute").click()
|
||||||
self.locate_confirm_dialog().get_by_text("Confirm").click()
|
self.locate_confirm_dialog().get_by_text("Confirm").click()
|
||||||
# Wait until bookmark list is updated (old reference becomes invisible)
|
# Wait until bookmark list is updated (old reference becomes invisible)
|
||||||
bookmark_list.wait_for_element_state("hidden", timeout=1000)
|
bookmark_list.wait_for_element_state("hidden", timeout=1000)
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
50,
|
50,
|
||||||
@@ -112,19 +110,18 @@ class BookmarkPageBulkEditE2ETestCase(LinkdingE2ETestCase):
|
|||||||
def test_active_bookmarks_bulk_select_across_respects_query(self):
|
def test_active_bookmarks_bulk_select_across_respects_query(self):
|
||||||
self.setup_test_data()
|
self.setup_test_data()
|
||||||
|
|
||||||
with sync_playwright() as p:
|
self.open(reverse("linkding:bookmarks.index") + "?q=foo")
|
||||||
self.open(reverse("linkding:bookmarks.index") + "?q=foo", p)
|
|
||||||
|
|
||||||
bookmark_list = self.locate_bookmark_list().element_handle()
|
bookmark_list = self.locate_bookmark_list().element_handle()
|
||||||
self.locate_bulk_edit_toggle().click()
|
self.locate_bulk_edit_toggle().click()
|
||||||
self.locate_bulk_edit_select_all().click()
|
self.locate_bulk_edit_select_all().click()
|
||||||
self.locate_bulk_edit_select_across().click()
|
self.locate_bulk_edit_select_across().click()
|
||||||
|
|
||||||
self.select_bulk_action("Delete")
|
self.select_bulk_action("Delete")
|
||||||
self.locate_bulk_edit_bar().get_by_text("Execute").click()
|
self.locate_bulk_edit_bar().get_by_text("Execute").click()
|
||||||
self.locate_confirm_dialog().get_by_text("Confirm").click()
|
self.locate_confirm_dialog().get_by_text("Confirm").click()
|
||||||
# Wait until bookmark list is updated (old reference becomes invisible)
|
# Wait until bookmark list is updated (old reference becomes invisible)
|
||||||
bookmark_list.wait_for_element_state("hidden", timeout=1000)
|
bookmark_list.wait_for_element_state("hidden", timeout=1000)
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
50,
|
50,
|
||||||
@@ -150,19 +147,18 @@ class BookmarkPageBulkEditE2ETestCase(LinkdingE2ETestCase):
|
|||||||
def test_archived_bookmarks_bulk_select_across_respects_query(self):
|
def test_archived_bookmarks_bulk_select_across_respects_query(self):
|
||||||
self.setup_test_data()
|
self.setup_test_data()
|
||||||
|
|
||||||
with sync_playwright() as p:
|
self.open(reverse("linkding:bookmarks.archived") + "?q=foo")
|
||||||
self.open(reverse("linkding:bookmarks.archived") + "?q=foo", p)
|
|
||||||
|
|
||||||
bookmark_list = self.locate_bookmark_list().element_handle()
|
bookmark_list = self.locate_bookmark_list().element_handle()
|
||||||
self.locate_bulk_edit_toggle().click()
|
self.locate_bulk_edit_toggle().click()
|
||||||
self.locate_bulk_edit_select_all().click()
|
self.locate_bulk_edit_select_all().click()
|
||||||
self.locate_bulk_edit_select_across().click()
|
self.locate_bulk_edit_select_across().click()
|
||||||
|
|
||||||
self.select_bulk_action("Delete")
|
self.select_bulk_action("Delete")
|
||||||
self.locate_bulk_edit_bar().get_by_text("Execute").click()
|
self.locate_bulk_edit_bar().get_by_text("Execute").click()
|
||||||
self.locate_confirm_dialog().get_by_text("Confirm").click()
|
self.locate_confirm_dialog().get_by_text("Confirm").click()
|
||||||
# Wait until bookmark list is updated (old reference becomes invisible)
|
# Wait until bookmark list is updated (old reference becomes invisible)
|
||||||
bookmark_list.wait_for_element_state("hidden", timeout=1000)
|
bookmark_list.wait_for_element_state("hidden", timeout=1000)
|
||||||
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
50,
|
50,
|
||||||
@@ -188,148 +184,138 @@ class BookmarkPageBulkEditE2ETestCase(LinkdingE2ETestCase):
|
|||||||
def test_select_all_toggles_all_checkboxes(self):
|
def test_select_all_toggles_all_checkboxes(self):
|
||||||
self.setup_numbered_bookmarks(5)
|
self.setup_numbered_bookmarks(5)
|
||||||
|
|
||||||
with sync_playwright() as p:
|
url = reverse("linkding:bookmarks.index")
|
||||||
url = reverse("linkding:bookmarks.index")
|
page = self.open(url)
|
||||||
page = self.open(url, p)
|
|
||||||
|
|
||||||
self.locate_bulk_edit_toggle().click()
|
self.locate_bulk_edit_toggle().click()
|
||||||
|
|
||||||
checkboxes = page.locator("label.bulk-edit-checkbox input")
|
checkboxes = page.locator("label.bulk-edit-checkbox input")
|
||||||
self.assertEqual(6, checkboxes.count())
|
self.assertEqual(6, checkboxes.count())
|
||||||
for i in range(checkboxes.count()):
|
for i in range(checkboxes.count()):
|
||||||
expect(checkboxes.nth(i)).not_to_be_checked()
|
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()):
|
for i in range(checkboxes.count()):
|
||||||
expect(checkboxes.nth(i)).to_be_checked()
|
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()):
|
for i in range(checkboxes.count()):
|
||||||
expect(checkboxes.nth(i)).not_to_be_checked()
|
expect(checkboxes.nth(i)).not_to_be_checked()
|
||||||
|
|
||||||
def test_select_all_shows_select_across(self):
|
def test_select_all_shows_select_across(self):
|
||||||
self.setup_numbered_bookmarks(5)
|
self.setup_numbered_bookmarks(5)
|
||||||
|
|
||||||
with sync_playwright() as p:
|
url = reverse("linkding:bookmarks.index")
|
||||||
url = reverse("linkding:bookmarks.index")
|
self.open(url)
|
||||||
self.open(url, p)
|
|
||||||
|
|
||||||
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()
|
self.locate_bulk_edit_select_all().click()
|
||||||
expect(self.locate_bulk_edit_select_across()).to_be_visible()
|
expect(self.locate_bulk_edit_select_across()).to_be_visible()
|
||||||
|
|
||||||
self.locate_bulk_edit_select_all().click()
|
self.locate_bulk_edit_select_all().click()
|
||||||
expect(self.locate_bulk_edit_select_across()).not_to_be_visible()
|
expect(self.locate_bulk_edit_select_across()).not_to_be_visible()
|
||||||
|
|
||||||
def test_select_across_is_unchecked_when_toggling_all(self):
|
def test_select_across_is_unchecked_when_toggling_all(self):
|
||||||
self.setup_numbered_bookmarks(5)
|
self.setup_numbered_bookmarks(5)
|
||||||
|
|
||||||
with sync_playwright() as p:
|
url = reverse("linkding:bookmarks.index")
|
||||||
url = reverse("linkding:bookmarks.index")
|
self.open(url)
|
||||||
self.open(url, p)
|
|
||||||
|
|
||||||
self.locate_bulk_edit_toggle().click()
|
self.locate_bulk_edit_toggle().click()
|
||||||
|
|
||||||
# Show select across, check it
|
# Show select across, check it
|
||||||
self.locate_bulk_edit_select_all().click()
|
self.locate_bulk_edit_select_all().click()
|
||||||
self.locate_bulk_edit_select_across().click()
|
self.locate_bulk_edit_select_across().click()
|
||||||
expect(self.locate_bulk_edit_select_across()).to_be_checked()
|
expect(self.locate_bulk_edit_select_across()).to_be_checked()
|
||||||
|
|
||||||
# Hide select across by toggling select all
|
# Hide select across by toggling select all
|
||||||
self.locate_bulk_edit_select_all().click()
|
self.locate_bulk_edit_select_all().click()
|
||||||
expect(self.locate_bulk_edit_select_across()).not_to_be_visible()
|
expect(self.locate_bulk_edit_select_across()).not_to_be_visible()
|
||||||
|
|
||||||
# Show select across again, verify it is unchecked
|
# Show select across again, verify it is unchecked
|
||||||
self.locate_bulk_edit_select_all().click()
|
self.locate_bulk_edit_select_all().click()
|
||||||
expect(self.locate_bulk_edit_select_across()).not_to_be_checked()
|
expect(self.locate_bulk_edit_select_across()).not_to_be_checked()
|
||||||
|
|
||||||
def test_select_across_is_unchecked_when_toggling_bookmark(self):
|
def test_select_across_is_unchecked_when_toggling_bookmark(self):
|
||||||
self.setup_numbered_bookmarks(5)
|
self.setup_numbered_bookmarks(5)
|
||||||
|
|
||||||
with sync_playwright() as p:
|
url = reverse("linkding:bookmarks.index")
|
||||||
url = reverse("linkding:bookmarks.index")
|
self.open(url)
|
||||||
self.open(url, p)
|
|
||||||
|
|
||||||
self.locate_bulk_edit_toggle().click()
|
self.locate_bulk_edit_toggle().click()
|
||||||
|
|
||||||
# Show select across, check it
|
# Show select across, check it
|
||||||
self.locate_bulk_edit_select_all().click()
|
self.locate_bulk_edit_select_all().click()
|
||||||
self.locate_bulk_edit_select_across().click()
|
self.locate_bulk_edit_select_across().click()
|
||||||
expect(self.locate_bulk_edit_select_across()).to_be_checked()
|
expect(self.locate_bulk_edit_select_across()).to_be_checked()
|
||||||
|
|
||||||
# Hide select across by toggling a single bookmark
|
# Hide select across by toggling a single bookmark
|
||||||
self.locate_bookmark("Bookmark 1").locator(
|
self.locate_bookmark("Bookmark 1").locator("label.bulk-edit-checkbox").click()
|
||||||
"label.bulk-edit-checkbox"
|
expect(self.locate_bulk_edit_select_across()).not_to_be_visible()
|
||||||
).click()
|
|
||||||
expect(self.locate_bulk_edit_select_across()).not_to_be_visible()
|
|
||||||
|
|
||||||
# Show select across again, verify it is unchecked
|
# Show select across again, verify it is unchecked
|
||||||
self.locate_bookmark("Bookmark 1").locator(
|
self.locate_bookmark("Bookmark 1").locator("label.bulk-edit-checkbox").click()
|
||||||
"label.bulk-edit-checkbox"
|
expect(self.locate_bulk_edit_select_across()).not_to_be_checked()
|
||||||
).click()
|
|
||||||
expect(self.locate_bulk_edit_select_across()).not_to_be_checked()
|
|
||||||
|
|
||||||
def test_execute_resets_all_checkboxes(self):
|
def test_execute_resets_all_checkboxes(self):
|
||||||
self.setup_numbered_bookmarks(100)
|
self.setup_numbered_bookmarks(100)
|
||||||
|
|
||||||
with sync_playwright() as p:
|
url = reverse("linkding:bookmarks.index")
|
||||||
url = reverse("linkding:bookmarks.index")
|
page = self.open(url)
|
||||||
page = self.open(url, p)
|
|
||||||
|
|
||||||
bookmark_list = self.locate_bookmark_list().element_handle()
|
bookmark_list = self.locate_bookmark_list().element_handle()
|
||||||
|
|
||||||
# Select all bookmarks, enable select across
|
# Select all bookmarks, enable select across
|
||||||
self.locate_bulk_edit_toggle().click()
|
self.locate_bulk_edit_toggle().click()
|
||||||
self.locate_bulk_edit_select_all().click()
|
self.locate_bulk_edit_select_all().click()
|
||||||
self.locate_bulk_edit_select_across().click()
|
self.locate_bulk_edit_select_across().click()
|
||||||
|
|
||||||
# Execute bulk action
|
# Execute bulk action
|
||||||
self.select_bulk_action("Mark as unread")
|
self.select_bulk_action("Mark as unread")
|
||||||
self.locate_bulk_edit_bar().get_by_text("Execute").click()
|
self.locate_bulk_edit_bar().get_by_text("Execute").click()
|
||||||
self.locate_confirm_dialog().get_by_text("Confirm").click()
|
self.locate_confirm_dialog().get_by_text("Confirm").click()
|
||||||
|
|
||||||
# Wait until bookmark list is updated (old reference becomes invisible)
|
# Wait until bookmark list is updated (old reference becomes invisible)
|
||||||
bookmark_list.wait_for_element_state("hidden", timeout=1000)
|
bookmark_list.wait_for_element_state("hidden", timeout=1000)
|
||||||
|
|
||||||
# Verify bulk edit checkboxes are reset
|
# Verify bulk edit checkboxes are reset
|
||||||
checkboxes = page.locator("label.bulk-edit-checkbox input")
|
checkboxes = page.locator("label.bulk-edit-checkbox input")
|
||||||
self.assertEqual(31, checkboxes.count())
|
self.assertEqual(31, checkboxes.count())
|
||||||
for i in range(checkboxes.count()):
|
for i in range(checkboxes.count()):
|
||||||
expect(checkboxes.nth(i)).not_to_be_checked()
|
expect(checkboxes.nth(i)).not_to_be_checked()
|
||||||
|
|
||||||
# Toggle select all and verify select across is reset
|
# Toggle select all and verify select across is reset
|
||||||
self.locate_bulk_edit_select_all().click()
|
self.locate_bulk_edit_select_all().click()
|
||||||
expect(self.locate_bulk_edit_select_across()).not_to_be_checked()
|
expect(self.locate_bulk_edit_select_across()).not_to_be_checked()
|
||||||
|
|
||||||
def test_update_select_across_bookmark_count(self):
|
def test_update_select_across_bookmark_count(self):
|
||||||
self.setup_numbered_bookmarks(100)
|
self.setup_numbered_bookmarks(100)
|
||||||
|
|
||||||
with sync_playwright() as p:
|
url = reverse("linkding:bookmarks.index")
|
||||||
url = reverse("linkding:bookmarks.index")
|
self.open(url)
|
||||||
self.open(url, p)
|
|
||||||
|
|
||||||
bookmark_list = self.locate_bookmark_list().element_handle()
|
bookmark_list = self.locate_bookmark_list().element_handle()
|
||||||
self.locate_bulk_edit_toggle().click()
|
self.locate_bulk_edit_toggle().click()
|
||||||
self.locate_bulk_edit_select_all().click()
|
self.locate_bulk_edit_select_all().click()
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
self.locate_bulk_edit_bar().get_by_text("All pages (100 bookmarks)")
|
self.locate_bulk_edit_bar().get_by_text("All pages (100 bookmarks)")
|
||||||
).to_be_visible()
|
).to_be_visible()
|
||||||
|
|
||||||
self.select_bulk_action("Delete")
|
self.select_bulk_action("Delete")
|
||||||
self.locate_bulk_edit_bar().get_by_text("Execute").click()
|
self.locate_bulk_edit_bar().get_by_text("Execute").click()
|
||||||
self.locate_confirm_dialog().get_by_text("Confirm").click()
|
self.locate_confirm_dialog().get_by_text("Confirm").click()
|
||||||
# Wait until bookmark list is updated (old reference becomes invisible)
|
# Wait until bookmark list is updated (old reference becomes invisible)
|
||||||
bookmark_list.wait_for_element_state("hidden", timeout=1000)
|
bookmark_list.wait_for_element_state("hidden", timeout=1000)
|
||||||
|
|
||||||
expect(self.locate_bulk_edit_select_all()).not_to_be_checked()
|
expect(self.locate_bulk_edit_select_all()).not_to_be_checked()
|
||||||
self.locate_bulk_edit_select_all().click()
|
self.locate_bulk_edit_select_all().click()
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
self.locate_bulk_edit_bar().get_by_text("All pages (70 bookmarks)")
|
self.locate_bulk_edit_bar().get_by_text("All pages (70 bookmarks)")
|
||||||
).to_be_visible()
|
).to_be_visible()
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
from django.urls import reverse
|
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.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="foo")
|
||||||
self.setup_numbered_bookmarks(5, prefix="bar")
|
self.setup_numbered_bookmarks(5, prefix="bar")
|
||||||
|
|
||||||
with sync_playwright() as p:
|
url = reverse("linkding:bookmarks.index") + "?q=foo"
|
||||||
url = reverse("linkding:bookmarks.index") + "?q=foo"
|
self.open(url)
|
||||||
self.open(url, p)
|
|
||||||
|
|
||||||
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.locate_bookmark("foo 2").get_by_text("Archive").click()
|
||||||
self.assertVisibleBookmarks(["foo 1", "foo 3", "foo 4", "foo 5"])
|
self.assertVisibleBookmarks(["foo 1", "foo 3", "foo 4", "foo 5"])
|
||||||
|
|
||||||
def test_partial_update_respects_sort(self):
|
def test_partial_update_respects_sort(self):
|
||||||
self.setup_numbered_bookmarks(5, prefix="foo")
|
self.setup_numbered_bookmarks(5, prefix="foo")
|
||||||
|
|
||||||
with sync_playwright() as p:
|
url = reverse("linkding:bookmarks.index") + "?sort=title_asc"
|
||||||
url = reverse("linkding:bookmarks.index") + "?sort=title_asc"
|
page = self.open(url)
|
||||||
page = self.open(url, p)
|
|
||||||
|
|
||||||
first_item = page.locator("ul.bookmark-list > li").first
|
first_item = page.locator("ul.bookmark-list > li").first
|
||||||
expect(first_item).to_contain_text("foo 1")
|
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
|
first_item = page.locator("ul.bookmark-list > li").first
|
||||||
expect(first_item).to_contain_text("foo 2")
|
expect(first_item).to_contain_text("foo 2")
|
||||||
|
|
||||||
def test_partial_update_respects_page(self):
|
def test_partial_update_respects_page(self):
|
||||||
# add a suffix, otherwise 'foo 1' also matches 'foo 10'
|
# add a suffix, otherwise 'foo 1' also matches 'foo 10'
|
||||||
self.setup_numbered_bookmarks(50, prefix="foo", suffix="-")
|
self.setup_numbered_bookmarks(50, prefix="foo", suffix="-")
|
||||||
|
|
||||||
with sync_playwright() as p:
|
url = reverse("linkding:bookmarks.index") + "?q=foo&page=2"
|
||||||
url = reverse("linkding:bookmarks.index") + "?q=foo&page=2"
|
self.open(url)
|
||||||
self.open(url, p)
|
|
||||||
|
|
||||||
# with descending sort, page two has 'foo 1' to 'foo 20'
|
# with descending sort, page two has 'foo 1' to 'foo 20'
|
||||||
expected_titles = [f"foo {i}-" for i in range(1, 21)]
|
expected_titles = [f"foo {i}-" for i in range(1, 21)]
|
||||||
self.assertVisibleBookmarks(expected_titles)
|
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)]
|
expected_titles = [f"foo {i}-" for i in range(1, 20)]
|
||||||
self.assertVisibleBookmarks(expected_titles)
|
self.assertVisibleBookmarks(expected_titles)
|
||||||
|
|
||||||
def test_multiple_partial_updates(self):
|
def test_multiple_partial_updates(self):
|
||||||
self.setup_numbered_bookmarks(5)
|
self.setup_numbered_bookmarks(5)
|
||||||
|
|
||||||
with sync_playwright() as p:
|
url = reverse("linkding:bookmarks.index")
|
||||||
url = reverse("linkding:bookmarks.index")
|
self.open(url)
|
||||||
self.open(url, p)
|
|
||||||
|
|
||||||
self.locate_bookmark("Bookmark 1").get_by_text("Archive").click()
|
self.locate_bookmark("Bookmark 1").get_by_text("Archive").click()
|
||||||
self.assertVisibleBookmarks(
|
self.assertVisibleBookmarks(
|
||||||
["Bookmark 2", "Bookmark 3", "Bookmark 4", "Bookmark 5"]
|
["Bookmark 2", "Bookmark 3", "Bookmark 4", "Bookmark 5"]
|
||||||
)
|
)
|
||||||
|
|
||||||
self.locate_bookmark("Bookmark 2").get_by_text("Archive").click()
|
self.locate_bookmark("Bookmark 2").get_by_text("Archive").click()
|
||||||
self.assertVisibleBookmarks(["Bookmark 3", "Bookmark 4", "Bookmark 5"])
|
self.assertVisibleBookmarks(["Bookmark 3", "Bookmark 4", "Bookmark 5"])
|
||||||
|
|
||||||
self.locate_bookmark("Bookmark 3").get_by_text("Archive").click()
|
self.locate_bookmark("Bookmark 3").get_by_text("Archive").click()
|
||||||
self.assertVisibleBookmarks(["Bookmark 4", "Bookmark 5"])
|
self.assertVisibleBookmarks(["Bookmark 4", "Bookmark 5"])
|
||||||
|
|
||||||
self.assertReloads(0)
|
self.assertReloads(0)
|
||||||
|
|
||||||
def test_active_bookmarks_partial_update_on_archive(self):
|
def test_active_bookmarks_partial_update_on_archive(self):
|
||||||
self.setup_fixture()
|
self.setup_fixture()
|
||||||
|
|
||||||
with sync_playwright() as p:
|
self.open(reverse("linkding:bookmarks.index"))
|
||||||
self.open(reverse("linkding:bookmarks.index"), p)
|
|
||||||
|
|
||||||
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.assertVisibleBookmarks(["Bookmark 1", "Bookmark 3"])
|
||||||
self.assertVisibleTags(["Tag 1", "Tag 3"])
|
self.assertVisibleTags(["Tag 1", "Tag 3"])
|
||||||
self.assertReloads(0)
|
self.assertReloads(0)
|
||||||
|
|
||||||
def test_active_bookmarks_partial_update_on_delete(self):
|
def test_active_bookmarks_partial_update_on_delete(self):
|
||||||
self.setup_fixture()
|
self.setup_fixture()
|
||||||
|
|
||||||
with sync_playwright() as p:
|
self.open(reverse("linkding:bookmarks.index"))
|
||||||
self.open(reverse("linkding:bookmarks.index"), p)
|
|
||||||
|
|
||||||
self.locate_bookmark("Bookmark 2").get_by_text("Remove").click()
|
self.locate_bookmark("Bookmark 2").get_by_text("Remove").click()
|
||||||
self.locate_confirm_dialog().get_by_text("Confirm").click()
|
self.locate_confirm_dialog().get_by_text("Confirm").click()
|
||||||
|
|
||||||
self.assertVisibleBookmarks(["Bookmark 1", "Bookmark 3"])
|
self.assertVisibleBookmarks(["Bookmark 1", "Bookmark 3"])
|
||||||
self.assertVisibleTags(["Tag 1", "Tag 3"])
|
self.assertVisibleTags(["Tag 1", "Tag 3"])
|
||||||
self.assertReloads(0)
|
self.assertReloads(0)
|
||||||
|
|
||||||
def test_active_bookmarks_partial_update_on_mark_as_read(self):
|
def test_active_bookmarks_partial_update_on_mark_as_read(self):
|
||||||
self.setup_fixture()
|
self.setup_fixture()
|
||||||
@@ -135,15 +129,14 @@ class BookmarkPagePartialUpdatesE2ETestCase(LinkdingE2ETestCase):
|
|||||||
bookmark2.unread = True
|
bookmark2.unread = True
|
||||||
bookmark2.save()
|
bookmark2.save()
|
||||||
|
|
||||||
with sync_playwright() as p:
|
self.open(reverse("linkding:bookmarks.index"))
|
||||||
self.open(reverse("linkding:bookmarks.index"), p)
|
|
||||||
|
|
||||||
expect(self.locate_bookmark("Bookmark 2")).to_have_class("unread")
|
expect(self.locate_bookmark("Bookmark 2")).to_have_class("unread")
|
||||||
self.locate_bookmark("Bookmark 2").get_by_text("Unread").click()
|
self.locate_bookmark("Bookmark 2").get_by_text("Unread").click()
|
||||||
self.locate_confirm_dialog().get_by_text("Confirm").click()
|
self.locate_confirm_dialog().get_by_text("Confirm").click()
|
||||||
|
|
||||||
expect(self.locate_bookmark("Bookmark 2")).not_to_have_class("unread")
|
expect(self.locate_bookmark("Bookmark 2")).not_to_have_class("unread")
|
||||||
self.assertReloads(0)
|
self.assertReloads(0)
|
||||||
|
|
||||||
def test_active_bookmarks_partial_update_on_unshare(self):
|
def test_active_bookmarks_partial_update_on_unshare(self):
|
||||||
self.setup_fixture()
|
self.setup_fixture()
|
||||||
@@ -151,112 +144,101 @@ class BookmarkPagePartialUpdatesE2ETestCase(LinkdingE2ETestCase):
|
|||||||
bookmark2.shared = True
|
bookmark2.shared = True
|
||||||
bookmark2.save()
|
bookmark2.save()
|
||||||
|
|
||||||
with sync_playwright() as p:
|
self.open(reverse("linkding:bookmarks.index"))
|
||||||
self.open(reverse("linkding:bookmarks.index"), p)
|
|
||||||
|
|
||||||
expect(self.locate_bookmark("Bookmark 2")).to_have_class("shared")
|
expect(self.locate_bookmark("Bookmark 2")).to_have_class("shared")
|
||||||
self.locate_bookmark("Bookmark 2").get_by_text("Shared").click()
|
self.locate_bookmark("Bookmark 2").get_by_text("Shared").click()
|
||||||
self.locate_confirm_dialog().get_by_text("Confirm").click()
|
self.locate_confirm_dialog().get_by_text("Confirm").click()
|
||||||
|
|
||||||
expect(self.locate_bookmark("Bookmark 2")).not_to_have_class("shared")
|
expect(self.locate_bookmark("Bookmark 2")).not_to_have_class("shared")
|
||||||
self.assertReloads(0)
|
self.assertReloads(0)
|
||||||
|
|
||||||
def test_active_bookmarks_partial_update_on_bulk_archive(self):
|
def test_active_bookmarks_partial_update_on_bulk_archive(self):
|
||||||
self.setup_fixture()
|
self.setup_fixture()
|
||||||
|
|
||||||
with sync_playwright() as p:
|
self.open(reverse("linkding:bookmarks.index"))
|
||||||
self.open(reverse("linkding:bookmarks.index"), p)
|
|
||||||
|
|
||||||
self.locate_bulk_edit_toggle().click()
|
self.locate_bulk_edit_toggle().click()
|
||||||
self.locate_bookmark("Bookmark 2").locator(
|
self.locate_bookmark("Bookmark 2").locator("label.bulk-edit-checkbox").click()
|
||||||
"label.bulk-edit-checkbox"
|
self.select_bulk_action("Archive")
|
||||||
).click()
|
self.locate_bulk_edit_bar().get_by_text("Execute").click()
|
||||||
self.select_bulk_action("Archive")
|
self.locate_confirm_dialog().get_by_text("Confirm").click()
|
||||||
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.assertVisibleBookmarks(["Bookmark 1", "Bookmark 3"])
|
||||||
self.assertVisibleTags(["Tag 1", "Tag 3"])
|
self.assertVisibleTags(["Tag 1", "Tag 3"])
|
||||||
self.assertReloads(0)
|
self.assertReloads(0)
|
||||||
|
|
||||||
def test_active_bookmarks_partial_update_on_bulk_delete(self):
|
def test_active_bookmarks_partial_update_on_bulk_delete(self):
|
||||||
self.setup_fixture()
|
self.setup_fixture()
|
||||||
|
|
||||||
with sync_playwright() as p:
|
self.open(reverse("linkding:bookmarks.index"))
|
||||||
self.open(reverse("linkding:bookmarks.index"), p)
|
|
||||||
|
|
||||||
self.locate_bulk_edit_toggle().click()
|
self.locate_bulk_edit_toggle().click()
|
||||||
self.locate_bookmark("Bookmark 2").locator(
|
self.locate_bookmark("Bookmark 2").locator("label.bulk-edit-checkbox").click()
|
||||||
"label.bulk-edit-checkbox"
|
self.select_bulk_action("Delete")
|
||||||
).click()
|
self.locate_bulk_edit_bar().get_by_text("Execute").click()
|
||||||
self.select_bulk_action("Delete")
|
self.locate_confirm_dialog().get_by_text("Confirm").click()
|
||||||
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.assertVisibleBookmarks(["Bookmark 1", "Bookmark 3"])
|
||||||
self.assertVisibleTags(["Tag 1", "Tag 3"])
|
self.assertVisibleTags(["Tag 1", "Tag 3"])
|
||||||
self.assertReloads(0)
|
self.assertReloads(0)
|
||||||
|
|
||||||
def test_archived_bookmarks_partial_update_on_unarchive(self):
|
def test_archived_bookmarks_partial_update_on_unarchive(self):
|
||||||
self.setup_fixture()
|
self.setup_fixture()
|
||||||
|
|
||||||
with sync_playwright() as p:
|
self.open(reverse("linkding:bookmarks.archived"))
|
||||||
self.open(reverse("linkding:bookmarks.archived"), p)
|
|
||||||
|
|
||||||
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.assertVisibleBookmarks(["Archived Bookmark 1", "Archived Bookmark 3"])
|
||||||
self.assertVisibleTags(["Archived Tag 1", "Archived Tag 3"])
|
self.assertVisibleTags(["Archived Tag 1", "Archived Tag 3"])
|
||||||
self.assertReloads(0)
|
self.assertReloads(0)
|
||||||
|
|
||||||
def test_archived_bookmarks_partial_update_on_delete(self):
|
def test_archived_bookmarks_partial_update_on_delete(self):
|
||||||
self.setup_fixture()
|
self.setup_fixture()
|
||||||
|
|
||||||
with sync_playwright() as p:
|
self.open(reverse("linkding:bookmarks.archived"))
|
||||||
self.open(reverse("linkding:bookmarks.archived"), p)
|
|
||||||
|
|
||||||
self.locate_bookmark("Archived Bookmark 2").get_by_text("Remove").click()
|
self.locate_bookmark("Archived Bookmark 2").get_by_text("Remove").click()
|
||||||
self.locate_confirm_dialog().get_by_text("Confirm").click()
|
self.locate_confirm_dialog().get_by_text("Confirm").click()
|
||||||
|
|
||||||
self.assertVisibleBookmarks(["Archived Bookmark 1", "Archived Bookmark 3"])
|
self.assertVisibleBookmarks(["Archived Bookmark 1", "Archived Bookmark 3"])
|
||||||
self.assertVisibleTags(["Archived Tag 1", "Archived Tag 3"])
|
self.assertVisibleTags(["Archived Tag 1", "Archived Tag 3"])
|
||||||
self.assertReloads(0)
|
self.assertReloads(0)
|
||||||
|
|
||||||
def test_archived_bookmarks_partial_update_on_bulk_unarchive(self):
|
def test_archived_bookmarks_partial_update_on_bulk_unarchive(self):
|
||||||
self.setup_fixture()
|
self.setup_fixture()
|
||||||
|
|
||||||
with sync_playwright() as p:
|
self.open(reverse("linkding:bookmarks.archived"))
|
||||||
self.open(reverse("linkding:bookmarks.archived"), p)
|
|
||||||
|
|
||||||
self.locate_bulk_edit_toggle().click()
|
self.locate_bulk_edit_toggle().click()
|
||||||
self.locate_bookmark("Archived Bookmark 2").locator(
|
self.locate_bookmark("Archived Bookmark 2").locator(
|
||||||
"label.bulk-edit-checkbox"
|
"label.bulk-edit-checkbox"
|
||||||
).click()
|
).click()
|
||||||
self.select_bulk_action("Unarchive")
|
self.select_bulk_action("Unarchive")
|
||||||
self.locate_bulk_edit_bar().get_by_text("Execute").click()
|
self.locate_bulk_edit_bar().get_by_text("Execute").click()
|
||||||
self.locate_confirm_dialog().get_by_text("Confirm").click()
|
self.locate_confirm_dialog().get_by_text("Confirm").click()
|
||||||
|
|
||||||
self.assertVisibleBookmarks(["Archived Bookmark 1", "Archived Bookmark 3"])
|
self.assertVisibleBookmarks(["Archived Bookmark 1", "Archived Bookmark 3"])
|
||||||
self.assertVisibleTags(["Archived Tag 1", "Archived Tag 3"])
|
self.assertVisibleTags(["Archived Tag 1", "Archived Tag 3"])
|
||||||
self.assertReloads(0)
|
self.assertReloads(0)
|
||||||
|
|
||||||
def test_archived_bookmarks_partial_update_on_bulk_delete(self):
|
def test_archived_bookmarks_partial_update_on_bulk_delete(self):
|
||||||
self.setup_fixture()
|
self.setup_fixture()
|
||||||
|
|
||||||
with sync_playwright() as p:
|
self.open(reverse("linkding:bookmarks.archived"))
|
||||||
self.open(reverse("linkding:bookmarks.archived"), p)
|
|
||||||
|
|
||||||
self.locate_bulk_edit_toggle().click()
|
self.locate_bulk_edit_toggle().click()
|
||||||
self.locate_bookmark("Archived Bookmark 2").locator(
|
self.locate_bookmark("Archived Bookmark 2").locator(
|
||||||
"label.bulk-edit-checkbox"
|
"label.bulk-edit-checkbox"
|
||||||
).click()
|
).click()
|
||||||
self.select_bulk_action("Delete")
|
self.select_bulk_action("Delete")
|
||||||
self.locate_bulk_edit_bar().get_by_text("Execute").click()
|
self.locate_bulk_edit_bar().get_by_text("Execute").click()
|
||||||
self.locate_confirm_dialog().get_by_text("Confirm").click()
|
self.locate_confirm_dialog().get_by_text("Confirm").click()
|
||||||
|
|
||||||
self.assertVisibleBookmarks(["Archived Bookmark 1", "Archived Bookmark 3"])
|
self.assertVisibleBookmarks(["Archived Bookmark 1", "Archived Bookmark 3"])
|
||||||
self.assertVisibleTags(["Archived Tag 1", "Archived Tag 3"])
|
self.assertVisibleTags(["Archived Tag 1", "Archived Tag 3"])
|
||||||
self.assertReloads(0)
|
self.assertReloads(0)
|
||||||
|
|
||||||
def test_shared_bookmarks_partial_update_on_unarchive(self):
|
def test_shared_bookmarks_partial_update_on_unarchive(self):
|
||||||
self.setup_fixture()
|
self.setup_fixture()
|
||||||
@@ -264,24 +246,23 @@ class BookmarkPagePartialUpdatesE2ETestCase(LinkdingE2ETestCase):
|
|||||||
3, shared=True, prefix="My Bookmark", with_tags=True
|
3, shared=True, prefix="My Bookmark", with_tags=True
|
||||||
)
|
)
|
||||||
|
|
||||||
with sync_playwright() as p:
|
self.open(reverse("linkding:bookmarks.shared"))
|
||||||
self.open(reverse("linkding:bookmarks.shared"), p)
|
|
||||||
|
|
||||||
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
|
# Shared bookmarks page also shows archived bookmarks, though it probably shouldn't
|
||||||
self.assertVisibleBookmarks(
|
self.assertVisibleBookmarks(
|
||||||
[
|
[
|
||||||
"My Bookmark 1",
|
"My Bookmark 1",
|
||||||
"My Bookmark 2",
|
"My Bookmark 2",
|
||||||
"My Bookmark 3",
|
"My Bookmark 3",
|
||||||
"Joe's Bookmark 1",
|
"Joe's Bookmark 1",
|
||||||
"Joe's Bookmark 2",
|
"Joe's Bookmark 2",
|
||||||
"Joe's Bookmark 3",
|
"Joe's Bookmark 3",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
self.assertVisibleTags(["Shared Tag 1", "Shared Tag 2", "Shared Tag 3"])
|
self.assertVisibleTags(["Shared Tag 1", "Shared Tag 2", "Shared Tag 3"])
|
||||||
self.assertReloads(0)
|
self.assertReloads(0)
|
||||||
|
|
||||||
def test_shared_bookmarks_partial_update_on_delete(self):
|
def test_shared_bookmarks_partial_update_on_delete(self):
|
||||||
self.setup_fixture()
|
self.setup_fixture()
|
||||||
@@ -289,20 +270,19 @@ class BookmarkPagePartialUpdatesE2ETestCase(LinkdingE2ETestCase):
|
|||||||
3, shared=True, prefix="My Bookmark", with_tags=True
|
3, shared=True, prefix="My Bookmark", with_tags=True
|
||||||
)
|
)
|
||||||
|
|
||||||
with sync_playwright() as p:
|
self.open(reverse("linkding:bookmarks.shared"))
|
||||||
self.open(reverse("linkding:bookmarks.shared"), p)
|
|
||||||
|
|
||||||
self.locate_bookmark("My Bookmark 2").get_by_text("Remove").click()
|
self.locate_bookmark("My Bookmark 2").get_by_text("Remove").click()
|
||||||
self.locate_confirm_dialog().get_by_text("Confirm").click()
|
self.locate_confirm_dialog().get_by_text("Confirm").click()
|
||||||
|
|
||||||
self.assertVisibleBookmarks(
|
self.assertVisibleBookmarks(
|
||||||
[
|
[
|
||||||
"My Bookmark 1",
|
"My Bookmark 1",
|
||||||
"My Bookmark 3",
|
"My Bookmark 3",
|
||||||
"Joe's Bookmark 1",
|
"Joe's Bookmark 1",
|
||||||
"Joe's Bookmark 2",
|
"Joe's Bookmark 2",
|
||||||
"Joe's Bookmark 3",
|
"Joe's Bookmark 3",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
self.assertVisibleTags(["Shared Tag 1", "Shared Tag 3"])
|
self.assertVisibleTags(["Shared Tag 1", "Shared Tag 3"])
|
||||||
self.assertReloads(0)
|
self.assertReloads(0)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from django.urls import reverse
|
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.tests_e2e.helpers import LinkdingE2ETestCase
|
||||||
|
|
||||||
@@ -9,39 +9,38 @@ class BookmarkItemE2ETestCase(LinkdingE2ETestCase):
|
|||||||
group1 = self.setup_numbered_bookmarks(3, prefix="foo")
|
group1 = self.setup_numbered_bookmarks(3, prefix="foo")
|
||||||
group2 = self.setup_numbered_bookmarks(3, prefix="bar")
|
group2 = self.setup_numbered_bookmarks(3, prefix="bar")
|
||||||
|
|
||||||
with sync_playwright() as p:
|
# shows all bookmarks initially
|
||||||
# shows all bookmarks initially
|
page = self.open(reverse("linkding:bundles.new"))
|
||||||
page = self.open(reverse("linkding:bundles.new"), p)
|
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
page.get_by_text(f"Found 6 bookmarks matching this bundle")
|
page.get_by_text(f"Found 6 bookmarks matching this bundle")
|
||||||
).to_be_visible()
|
).to_be_visible()
|
||||||
self.assertVisibleBookmarks(group1 + group2)
|
self.assertVisibleBookmarks(group1 + group2)
|
||||||
|
|
||||||
# filter by group1
|
# filter by group1
|
||||||
search = page.get_by_label("Search")
|
search = page.get_by_label("Search")
|
||||||
search.fill("foo")
|
search.fill("foo")
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
page.get_by_text(f"Found 3 bookmarks matching this bundle")
|
page.get_by_text(f"Found 3 bookmarks matching this bundle")
|
||||||
).to_be_visible()
|
).to_be_visible()
|
||||||
self.assertVisibleBookmarks(group1)
|
self.assertVisibleBookmarks(group1)
|
||||||
|
|
||||||
# filter by group2
|
# filter by group2
|
||||||
search.fill("bar")
|
search.fill("bar")
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
page.get_by_text(f"Found 3 bookmarks matching this bundle")
|
page.get_by_text(f"Found 3 bookmarks matching this bundle")
|
||||||
).to_be_visible()
|
).to_be_visible()
|
||||||
self.assertVisibleBookmarks(group2)
|
self.assertVisibleBookmarks(group2)
|
||||||
|
|
||||||
# filter by invalid group
|
# filter by invalid group
|
||||||
search.fill("invalid")
|
search.fill("invalid")
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
page.get_by_text(f"No bookmarks match the current bundle")
|
page.get_by_text(f"No bookmarks match the current bundle")
|
||||||
).to_be_visible()
|
).to_be_visible()
|
||||||
self.assertVisibleBookmarks([])
|
self.assertVisibleBookmarks([])
|
||||||
|
|
||||||
def assertVisibleBookmarks(self, bookmarks):
|
def assertVisibleBookmarks(self, bookmarks):
|
||||||
self.assertEqual(len(bookmarks), self.count_bookmarks())
|
self.assertEqual(len(bookmarks), self.count_bookmarks())
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from django.urls import reverse
|
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.tests_e2e.helpers import LinkdingE2ETestCase
|
||||||
|
|
||||||
@@ -22,31 +22,25 @@ class CollapseSidePanelE2ETestCase(LinkdingE2ETestCase):
|
|||||||
).to_be_visible()
|
).to_be_visible()
|
||||||
|
|
||||||
def test_side_panel_should_be_visible_by_default(self):
|
def test_side_panel_should_be_visible_by_default(self):
|
||||||
with sync_playwright() as p:
|
self.open(reverse("linkding:bookmarks.index"))
|
||||||
self.open(reverse("linkding:bookmarks.index"), p)
|
self.assertSidePanelIsVisible()
|
||||||
self.assertSidePanelIsVisible()
|
|
||||||
|
|
||||||
self.page.goto(
|
self.page.goto(self.live_server_url + reverse("linkding:bookmarks.archived"))
|
||||||
self.live_server_url + reverse("linkding:bookmarks.archived")
|
self.assertSidePanelIsVisible()
|
||||||
)
|
|
||||||
self.assertSidePanelIsVisible()
|
|
||||||
|
|
||||||
self.page.goto(self.live_server_url + reverse("linkding:bookmarks.shared"))
|
self.page.goto(self.live_server_url + reverse("linkding:bookmarks.shared"))
|
||||||
self.assertSidePanelIsVisible()
|
self.assertSidePanelIsVisible()
|
||||||
|
|
||||||
def test_side_panel_should_be_hidden_when_collapsed(self):
|
def test_side_panel_should_be_hidden_when_collapsed(self):
|
||||||
user = self.get_or_create_test_user()
|
user = self.get_or_create_test_user()
|
||||||
user.profile.collapse_side_panel = True
|
user.profile.collapse_side_panel = True
|
||||||
user.profile.save()
|
user.profile.save()
|
||||||
|
|
||||||
with sync_playwright() as p:
|
self.open(reverse("linkding:bookmarks.index"))
|
||||||
self.open(reverse("linkding:bookmarks.index"), p)
|
self.assertSidePanelIsHidden()
|
||||||
self.assertSidePanelIsHidden()
|
|
||||||
|
|
||||||
self.page.goto(
|
self.page.goto(self.live_server_url + reverse("linkding:bookmarks.archived"))
|
||||||
self.live_server_url + reverse("linkding:bookmarks.archived")
|
self.assertSidePanelIsHidden()
|
||||||
)
|
|
||||||
self.assertSidePanelIsHidden()
|
|
||||||
|
|
||||||
self.page.goto(self.live_server_url + reverse("linkding:bookmarks.shared"))
|
self.page.goto(self.live_server_url + reverse("linkding:bookmarks.shared"))
|
||||||
self.assertSidePanelIsHidden()
|
self.assertSidePanelIsHidden()
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from django.urls import reverse
|
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.tests_e2e.helpers import LinkdingE2ETestCase
|
||||||
|
|
||||||
@@ -18,111 +18,105 @@ class DropdownE2ETestCase(LinkdingE2ETestCase):
|
|||||||
return self.locate_dropdown().locator(".menu")
|
return self.locate_dropdown().locator(".menu")
|
||||||
|
|
||||||
def test_click_toggle_opens_and_closes_dropdown(self):
|
def test_click_toggle_opens_and_closes_dropdown(self):
|
||||||
with sync_playwright() as p:
|
self.open(reverse("linkding:bookmarks.index"))
|
||||||
self.open(reverse("linkding:bookmarks.index"), p)
|
|
||||||
|
|
||||||
toggle = self.locate_dropdown_toggle()
|
toggle = self.locate_dropdown_toggle()
|
||||||
menu = self.locate_dropdown_menu()
|
menu = self.locate_dropdown_menu()
|
||||||
|
|
||||||
# Open dropdown
|
# Open dropdown
|
||||||
toggle.click()
|
toggle.click()
|
||||||
expect(menu).to_be_visible()
|
expect(menu).to_be_visible()
|
||||||
|
|
||||||
# Click toggle again to close
|
# Click toggle again to close
|
||||||
toggle.click()
|
toggle.click()
|
||||||
expect(menu).not_to_be_visible()
|
expect(menu).not_to_be_visible()
|
||||||
|
|
||||||
def test_outside_click_closes_dropdown(self):
|
def test_outside_click_closes_dropdown(self):
|
||||||
with sync_playwright() as p:
|
self.open(reverse("linkding:bookmarks.index"))
|
||||||
self.open(reverse("linkding:bookmarks.index"), p)
|
|
||||||
|
|
||||||
toggle = self.locate_dropdown_toggle()
|
toggle = self.locate_dropdown_toggle()
|
||||||
menu = self.locate_dropdown_menu()
|
menu = self.locate_dropdown_menu()
|
||||||
|
|
||||||
# Open dropdown
|
# Open dropdown
|
||||||
toggle.click()
|
toggle.click()
|
||||||
expect(menu).to_be_visible()
|
expect(menu).to_be_visible()
|
||||||
|
|
||||||
# Click outside the dropdown (on the page body)
|
# Click outside the dropdown (on the page body)
|
||||||
self.page.locator("main").click()
|
self.page.locator("main").click()
|
||||||
expect(menu).not_to_be_visible()
|
expect(menu).not_to_be_visible()
|
||||||
|
|
||||||
def test_escape_closes_dropdown_and_restores_focus(self):
|
def test_escape_closes_dropdown_and_restores_focus(self):
|
||||||
with sync_playwright() as p:
|
self.open(reverse("linkding:bookmarks.index"))
|
||||||
self.open(reverse("linkding:bookmarks.index"), p)
|
|
||||||
|
|
||||||
toggle = self.locate_dropdown_toggle()
|
toggle = self.locate_dropdown_toggle()
|
||||||
menu = self.locate_dropdown_menu()
|
menu = self.locate_dropdown_menu()
|
||||||
|
|
||||||
# Open dropdown
|
# Open dropdown
|
||||||
toggle.click()
|
toggle.click()
|
||||||
expect(menu).to_be_visible()
|
expect(menu).to_be_visible()
|
||||||
|
|
||||||
# Press Escape
|
# Press Escape
|
||||||
self.page.keyboard.press("Escape")
|
self.page.keyboard.press("Escape")
|
||||||
|
|
||||||
# Menu should be closed
|
# Menu should be closed
|
||||||
expect(menu).not_to_be_visible()
|
expect(menu).not_to_be_visible()
|
||||||
|
|
||||||
# Focus should be back on toggle
|
# Focus should be back on toggle
|
||||||
expect(toggle).to_be_focused()
|
expect(toggle).to_be_focused()
|
||||||
|
|
||||||
def test_focus_out_closes_dropdown(self):
|
def test_focus_out_closes_dropdown(self):
|
||||||
with sync_playwright() as p:
|
self.open(reverse("linkding:bookmarks.index"))
|
||||||
self.open(reverse("linkding:bookmarks.index"), p)
|
|
||||||
|
|
||||||
toggle = self.locate_dropdown_toggle()
|
toggle = self.locate_dropdown_toggle()
|
||||||
menu = self.locate_dropdown_menu()
|
menu = self.locate_dropdown_menu()
|
||||||
|
|
||||||
# Open dropdown
|
# Open dropdown
|
||||||
toggle.click()
|
toggle.click()
|
||||||
expect(menu).to_be_visible()
|
expect(menu).to_be_visible()
|
||||||
|
|
||||||
# Shift+Tab to move focus out of the dropdown
|
# Shift+Tab to move focus out of the dropdown
|
||||||
self.page.keyboard.press("Shift+Tab")
|
self.page.keyboard.press("Shift+Tab")
|
||||||
|
|
||||||
# Menu should be closed after focus leaves
|
# Menu should be closed after focus leaves
|
||||||
expect(menu).not_to_be_visible()
|
expect(menu).not_to_be_visible()
|
||||||
|
|
||||||
def test_aria_expanded_attribute(self):
|
def test_aria_expanded_attribute(self):
|
||||||
with sync_playwright() as p:
|
self.open(reverse("linkding:bookmarks.index"))
|
||||||
self.open(reverse("linkding:bookmarks.index"), p)
|
|
||||||
|
|
||||||
toggle = self.locate_dropdown_toggle()
|
toggle = self.locate_dropdown_toggle()
|
||||||
menu = self.locate_dropdown_menu()
|
menu = self.locate_dropdown_menu()
|
||||||
|
|
||||||
# Initially aria-expanded should be false
|
# Initially aria-expanded should be false
|
||||||
expect(toggle).to_have_attribute("aria-expanded", "false")
|
expect(toggle).to_have_attribute("aria-expanded", "false")
|
||||||
|
|
||||||
# Open dropdown
|
# Open dropdown
|
||||||
toggle.click()
|
toggle.click()
|
||||||
expect(menu).to_be_visible()
|
expect(menu).to_be_visible()
|
||||||
|
|
||||||
# aria-expanded should be true
|
# aria-expanded should be true
|
||||||
expect(toggle).to_have_attribute("aria-expanded", "true")
|
expect(toggle).to_have_attribute("aria-expanded", "true")
|
||||||
|
|
||||||
# Close dropdown
|
# Close dropdown
|
||||||
toggle.click()
|
toggle.click()
|
||||||
expect(menu).not_to_be_visible()
|
expect(menu).not_to_be_visible()
|
||||||
|
|
||||||
# aria-expanded should be false again
|
# aria-expanded should be false again
|
||||||
expect(toggle).to_have_attribute("aria-expanded", "false")
|
expect(toggle).to_have_attribute("aria-expanded", "false")
|
||||||
|
|
||||||
def test_can_click_menu_item(self):
|
def test_can_click_menu_item(self):
|
||||||
with sync_playwright() as p:
|
self.open(reverse("linkding:bookmarks.index"))
|
||||||
self.open(reverse("linkding:bookmarks.index"), p)
|
|
||||||
|
|
||||||
toggle = self.locate_dropdown_toggle()
|
toggle = self.locate_dropdown_toggle()
|
||||||
menu = self.locate_dropdown_menu()
|
menu = self.locate_dropdown_menu()
|
||||||
|
|
||||||
# Open dropdown
|
# Open dropdown
|
||||||
toggle.click()
|
toggle.click()
|
||||||
expect(menu).to_be_visible()
|
expect(menu).to_be_visible()
|
||||||
|
|
||||||
# Click on "Archived" menu item
|
# Click on "Archived" menu item
|
||||||
menu.get_by_text("Archived", exact=True).click()
|
menu.get_by_text("Archived", exact=True).click()
|
||||||
|
|
||||||
# Should navigate to archived page
|
# Should navigate to archived page
|
||||||
expect(self.page).to_have_url(
|
expect(self.page).to_have_url(
|
||||||
self.live_server_url + reverse("linkding:bookmarks.archived")
|
self.live_server_url + reverse("linkding:bookmarks.archived")
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
from django.urls import reverse
|
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.tests_e2e.helpers import LinkdingE2ETestCase
|
||||||
from bookmarks.services import website_loader
|
from bookmarks.services import website_loader
|
||||||
@@ -24,51 +24,47 @@ class BookmarkFormE2ETestCase(LinkdingE2ETestCase):
|
|||||||
self.website_loader_patch.start()
|
self.website_loader_patch.start()
|
||||||
|
|
||||||
def tearDown(self) -> None:
|
def tearDown(self) -> None:
|
||||||
super().tearDown()
|
|
||||||
self.website_loader_patch.stop()
|
self.website_loader_patch.stop()
|
||||||
|
super().tearDown()
|
||||||
|
|
||||||
def test_should_not_check_for_existing_bookmark(self):
|
def test_should_not_check_for_existing_bookmark(self):
|
||||||
bookmark = self.setup_bookmark()
|
bookmark = self.setup_bookmark()
|
||||||
|
|
||||||
with sync_playwright() as p:
|
page = self.open(reverse("linkding:bookmarks.edit", args=[bookmark.id]))
|
||||||
page = self.open(reverse("linkding:bookmarks.edit", args=[bookmark.id]), p)
|
|
||||||
|
|
||||||
page.wait_for_timeout(timeout=1000)
|
page.wait_for_timeout(timeout=1000)
|
||||||
page.get_by_text("This URL is already bookmarked.").wait_for(state="hidden")
|
page.get_by_text("This URL is already bookmarked.").wait_for(state="hidden")
|
||||||
|
|
||||||
def test_should_not_prefill_title_and_description(self):
|
def test_should_not_prefill_title_and_description(self):
|
||||||
bookmark = self.setup_bookmark(
|
bookmark = self.setup_bookmark(
|
||||||
title="Initial title", description="Initial description"
|
title="Initial title", description="Initial description"
|
||||||
)
|
)
|
||||||
|
|
||||||
with sync_playwright() as p:
|
page = self.open(reverse("linkding:bookmarks.edit", args=[bookmark.id]))
|
||||||
page = self.open(reverse("linkding:bookmarks.edit", args=[bookmark.id]), p)
|
page.wait_for_timeout(timeout=1000)
|
||||||
page.wait_for_timeout(timeout=1000)
|
|
||||||
|
|
||||||
title = page.get_by_label("Title")
|
title = page.get_by_label("Title")
|
||||||
description = page.get_by_label("Description")
|
description = page.get_by_label("Description")
|
||||||
expect(title).to_have_value(bookmark.title)
|
expect(title).to_have_value(bookmark.title)
|
||||||
expect(description).to_have_value(bookmark.description)
|
expect(description).to_have_value(bookmark.description)
|
||||||
|
|
||||||
def test_enter_url_should_not_prefill_title_and_description(self):
|
def test_enter_url_should_not_prefill_title_and_description(self):
|
||||||
bookmark = self.setup_bookmark()
|
bookmark = self.setup_bookmark()
|
||||||
|
|
||||||
with sync_playwright() as p:
|
page = self.open(reverse("linkding:bookmarks.edit", args=[bookmark.id]))
|
||||||
page = self.open(reverse("linkding:bookmarks.edit", args=[bookmark.id]), p)
|
|
||||||
|
|
||||||
page.get_by_label("URL").fill("https://example.com")
|
page.get_by_label("URL").fill("https://example.com")
|
||||||
page.wait_for_timeout(timeout=1000)
|
page.wait_for_timeout(timeout=1000)
|
||||||
|
|
||||||
title = page.get_by_label("Title")
|
title = page.get_by_label("Title")
|
||||||
description = page.get_by_label("Description")
|
description = page.get_by_label("Description")
|
||||||
expect(title).to_have_value(bookmark.title)
|
expect(title).to_have_value(bookmark.title)
|
||||||
expect(description).to_have_value(bookmark.description)
|
expect(description).to_have_value(bookmark.description)
|
||||||
|
|
||||||
def test_refresh_button_should_be_visible_when_editing(self):
|
def test_refresh_button_should_be_visible_when_editing(self):
|
||||||
bookmark = self.setup_bookmark()
|
bookmark = self.setup_bookmark()
|
||||||
|
|
||||||
with sync_playwright() as p:
|
page = self.open(reverse("linkding:bookmarks.edit", args=[bookmark.id]))
|
||||||
page = self.open(reverse("linkding:bookmarks.edit", args=[bookmark.id]), p)
|
|
||||||
|
|
||||||
refresh_button = page.get_by_text("Refresh from website")
|
refresh_button = page.get_by_text("Refresh from website")
|
||||||
expect(refresh_button).to_be_visible()
|
expect(refresh_button).to_be_visible()
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from django.urls import reverse
|
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.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="cooking")])
|
||||||
self.setup_bookmark(tags=[self.setup_tag(name="hiking")])
|
self.setup_bookmark(tags=[self.setup_tag(name="hiking")])
|
||||||
|
|
||||||
with sync_playwright() as p:
|
page = self.open(reverse("linkding:bookmarks.index"))
|
||||||
page = self.open(reverse("linkding:bookmarks.index"), p)
|
|
||||||
|
|
||||||
# use smaller viewport to make filter button visible
|
# use smaller viewport to make filter button visible
|
||||||
page.set_viewport_size({"width": 375, "height": 812})
|
page.set_viewport_size({"width": 375, "height": 812})
|
||||||
|
|
||||||
# open drawer
|
# open drawer
|
||||||
drawer_trigger = page.locator(".main").get_by_role("button", name="Filters")
|
drawer_trigger = page.locator(".main").get_by_role("button", name="Filters")
|
||||||
drawer_trigger.click()
|
drawer_trigger.click()
|
||||||
|
|
||||||
# verify drawer is visible
|
# verify drawer is visible
|
||||||
drawer = page.locator("ld-filter-drawer")
|
drawer = page.locator("ld-filter-drawer")
|
||||||
expect(drawer).to_be_visible()
|
expect(drawer).to_be_visible()
|
||||||
expect(drawer.locator("h2")).to_have_text("Filters")
|
expect(drawer.locator("h2")).to_have_text("Filters")
|
||||||
|
|
||||||
# close with close button
|
# close with close button
|
||||||
drawer.locator("button.close").click()
|
drawer.locator("button.close").click()
|
||||||
expect(drawer).to_be_hidden()
|
expect(drawer).to_be_hidden()
|
||||||
|
|
||||||
# open drawer again
|
# open drawer again
|
||||||
drawer_trigger.click()
|
drawer_trigger.click()
|
||||||
|
|
||||||
# close with backdrop
|
# close with backdrop
|
||||||
backdrop = drawer.locator(".modal-overlay")
|
backdrop = drawer.locator(".modal-overlay")
|
||||||
backdrop.click(position={"x": 0, "y": 0})
|
backdrop.click(position={"x": 0, "y": 0})
|
||||||
expect(drawer).to_be_hidden()
|
expect(drawer).to_be_hidden()
|
||||||
|
|
||||||
def test_select_tag(self):
|
def test_select_tag(self):
|
||||||
self.setup_bookmark(tags=[self.setup_tag(name="cooking")])
|
self.setup_bookmark(tags=[self.setup_tag(name="cooking")])
|
||||||
self.setup_bookmark(tags=[self.setup_tag(name="hiking")])
|
self.setup_bookmark(tags=[self.setup_tag(name="hiking")])
|
||||||
|
|
||||||
with sync_playwright() as p:
|
page = self.open(reverse("linkding:bookmarks.index"))
|
||||||
page = self.open(reverse("linkding:bookmarks.index"), p)
|
|
||||||
|
|
||||||
# use smaller viewport to make filter button visible
|
# use smaller viewport to make filter button visible
|
||||||
page.set_viewport_size({"width": 375, "height": 812})
|
page.set_viewport_size({"width": 375, "height": 812})
|
||||||
|
|
||||||
# open tag cloud modal
|
# open tag cloud modal
|
||||||
drawer_trigger = page.locator(".main").get_by_role("button", name="Filters")
|
drawer_trigger = page.locator(".main").get_by_role("button", name="Filters")
|
||||||
drawer_trigger.click()
|
drawer_trigger.click()
|
||||||
|
|
||||||
# verify tags are displayed
|
# verify tags are displayed
|
||||||
drawer = page.locator("ld-filter-drawer")
|
drawer = page.locator("ld-filter-drawer")
|
||||||
unselected_tags = drawer.locator(".unselected-tags")
|
unselected_tags = drawer.locator(".unselected-tags")
|
||||||
expect(unselected_tags.get_by_text("cooking")).to_be_visible()
|
expect(unselected_tags.get_by_text("cooking")).to_be_visible()
|
||||||
expect(unselected_tags.get_by_text("hiking")).to_be_visible()
|
expect(unselected_tags.get_by_text("hiking")).to_be_visible()
|
||||||
|
|
||||||
# select tag
|
# select tag
|
||||||
unselected_tags.get_by_text("cooking").click()
|
unselected_tags.get_by_text("cooking").click()
|
||||||
|
|
||||||
# open drawer again
|
# open drawer again
|
||||||
drawer_trigger.click()
|
drawer_trigger.click()
|
||||||
|
|
||||||
# verify tag is selected, other tag is not visible anymore
|
# verify tag is selected, other tag is not visible anymore
|
||||||
selected_tags = drawer.locator(".selected-tags")
|
selected_tags = drawer.locator(".selected-tags")
|
||||||
expect(selected_tags.get_by_text("cooking")).to_be_visible()
|
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("cooking")).not_to_be_visible()
|
||||||
expect(unselected_tags.get_by_text("hiking")).not_to_be_visible()
|
expect(unselected_tags.get_by_text("hiking")).not_to_be_visible()
|
||||||
|
|||||||
@@ -1,32 +1,24 @@
|
|||||||
from django.urls import reverse
|
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.tests_e2e.helpers import LinkdingE2ETestCase
|
||||||
|
|
||||||
|
|
||||||
class GlobalShortcutsE2ETestCase(LinkdingE2ETestCase):
|
class GlobalShortcutsE2ETestCase(LinkdingE2ETestCase):
|
||||||
def test_focus_search(self):
|
def test_focus_search(self):
|
||||||
with sync_playwright() as p:
|
self.open(reverse("linkding:bookmarks.index"))
|
||||||
browser = self.setup_browser(p)
|
|
||||||
page = browser.new_page()
|
|
||||||
page.goto(self.live_server_url + 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()
|
expect(
|
||||||
|
self.page.get_by_placeholder("Search for words or #tags")
|
||||||
browser.close()
|
).to_be_focused()
|
||||||
|
|
||||||
def test_add_bookmark(self):
|
def test_add_bookmark(self):
|
||||||
with sync_playwright() as p:
|
self.open(reverse("linkding:bookmarks.index"))
|
||||||
browser = self.setup_browser(p)
|
|
||||||
page = browser.new_page()
|
|
||||||
page.goto(self.live_server_url + reverse("linkding:bookmarks.index"))
|
|
||||||
|
|
||||||
page.press("body", "n")
|
self.page.press("body", "n")
|
||||||
|
|
||||||
expect(page).to_have_url(
|
expect(self.page).to_have_url(
|
||||||
self.live_server_url + reverse("linkding:bookmarks.new")
|
self.live_server_url + reverse("linkding:bookmarks.new")
|
||||||
)
|
)
|
||||||
|
|
||||||
browser.close()
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from unittest.mock import patch
|
|||||||
from urllib.parse import quote
|
from urllib.parse import quote
|
||||||
|
|
||||||
from django.urls import reverse
|
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.models import Bookmark
|
||||||
from bookmarks.services import website_loader
|
from bookmarks.services import website_loader
|
||||||
@@ -26,74 +26,69 @@ class BookmarkFormE2ETestCase(LinkdingE2ETestCase):
|
|||||||
self.website_loader_mock = self.website_loader_patch.start()
|
self.website_loader_mock = self.website_loader_patch.start()
|
||||||
|
|
||||||
def tearDown(self) -> None:
|
def tearDown(self) -> None:
|
||||||
super().tearDown()
|
|
||||||
self.website_loader_patch.stop()
|
self.website_loader_patch.stop()
|
||||||
|
super().tearDown()
|
||||||
|
|
||||||
def test_enter_url_prefills_title_and_description(self):
|
def test_enter_url_prefills_title_and_description(self):
|
||||||
with sync_playwright() as p:
|
page = self.open(reverse("linkding:bookmarks.new"))
|
||||||
page = self.open(reverse("linkding:bookmarks.new"), p)
|
url = page.get_by_label("URL")
|
||||||
url = page.get_by_label("URL")
|
title = page.get_by_label("Title")
|
||||||
title = page.get_by_label("Title")
|
description = page.get_by_label("Description")
|
||||||
description = page.get_by_label("Description")
|
|
||||||
|
|
||||||
url.fill("https://example.com")
|
url.fill("https://example.com")
|
||||||
expect(title).to_have_value("Example Domain")
|
expect(title).to_have_value("Example Domain")
|
||||||
expect(description).to_have_value(
|
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."
|
"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):
|
def test_enter_url_does_not_overwrite_modified_title_and_description(self):
|
||||||
with sync_playwright() as p:
|
page = self.open(reverse("linkding:bookmarks.new"))
|
||||||
page = self.open(reverse("linkding:bookmarks.new"), p)
|
url = page.get_by_label("URL")
|
||||||
url = page.get_by_label("URL")
|
title = page.get_by_label("Title")
|
||||||
title = page.get_by_label("Title")
|
description = page.get_by_label("Description")
|
||||||
description = page.get_by_label("Description")
|
|
||||||
|
|
||||||
title.fill("Modified title")
|
title.fill("Modified title")
|
||||||
description.fill("Modified description")
|
description.fill("Modified description")
|
||||||
url.fill("https://example.com")
|
url.fill("https://example.com")
|
||||||
page.wait_for_timeout(timeout=1000)
|
page.wait_for_timeout(timeout=1000)
|
||||||
|
|
||||||
expect(title).to_have_value("Modified title")
|
expect(title).to_have_value("Modified title")
|
||||||
expect(description).to_have_value("Modified description")
|
expect(description).to_have_value("Modified description")
|
||||||
|
|
||||||
def test_with_initial_url_prefills_title_and_description(self):
|
def test_with_initial_url_prefills_title_and_description(self):
|
||||||
with sync_playwright() as p:
|
page_url = (
|
||||||
page_url = (
|
reverse("linkding:bookmarks.new") + f"?url={quote('https://example.com')}"
|
||||||
reverse("linkding:bookmarks.new")
|
)
|
||||||
+ f"?url={quote('https://example.com')}"
|
page = self.open(page_url)
|
||||||
)
|
url = page.get_by_label("URL")
|
||||||
page = self.open(page_url, p)
|
title = page.get_by_label("Title")
|
||||||
url = page.get_by_label("URL")
|
description = page.get_by_label("Description")
|
||||||
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(url).to_have_value("https://example.com")
|
||||||
expect(title).to_have_value("Example Domain")
|
expect(title).to_have_value("Example Domain")
|
||||||
expect(description).to_have_value(
|
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."
|
"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(
|
def test_with_initial_url_title_description_does_not_overwrite_title_and_description(
|
||||||
self,
|
self,
|
||||||
):
|
):
|
||||||
with sync_playwright() as p:
|
page_url = (
|
||||||
page_url = (
|
reverse("linkding:bookmarks.new")
|
||||||
reverse("linkding:bookmarks.new")
|
+ f"?url={quote('https://example.com')}&title=Initial+title&description=Initial+description"
|
||||||
+ f"?url={quote('https://example.com')}&title=Initial+title&description=Initial+description"
|
)
|
||||||
)
|
page = self.open(page_url)
|
||||||
page = self.open(page_url, p)
|
url = page.get_by_label("URL")
|
||||||
url = page.get_by_label("URL")
|
title = page.get_by_label("Title")
|
||||||
title = page.get_by_label("Title")
|
description = page.get_by_label("Description")
|
||||||
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(url).to_have_value("https://example.com")
|
||||||
expect(title).to_have_value("Initial title")
|
expect(title).to_have_value("Initial title")
|
||||||
expect(description).to_have_value("Initial description")
|
expect(description).to_have_value("Initial description")
|
||||||
|
|
||||||
def test_create_should_check_for_existing_bookmark(self):
|
def test_create_should_check_for_existing_bookmark(self):
|
||||||
existing_bookmark = self.setup_bookmark(
|
existing_bookmark = self.setup_bookmark(
|
||||||
@@ -105,170 +100,164 @@ class BookmarkFormE2ETestCase(LinkdingE2ETestCase):
|
|||||||
)
|
)
|
||||||
tag_names = " ".join(existing_bookmark.tag_names)
|
tag_names = " ".join(existing_bookmark.tag_names)
|
||||||
|
|
||||||
with sync_playwright() as p:
|
page = self.open(reverse("linkding:bookmarks.new"))
|
||||||
page = self.open(reverse("linkding:bookmarks.new"), p)
|
|
||||||
|
|
||||||
# Enter bookmarked URL
|
# Enter bookmarked URL
|
||||||
page.get_by_label("URL").fill(existing_bookmark.url)
|
page.get_by_label("URL").fill(existing_bookmark.url)
|
||||||
# Already bookmarked hint should be visible
|
# Already bookmarked hint should be visible
|
||||||
page.get_by_text("This URL is already bookmarked.").wait_for(timeout=2000)
|
page.get_by_text("This URL is already bookmarked.").wait_for(timeout=2000)
|
||||||
# Form should be pre-filled with data from existing bookmark
|
# Form should be pre-filled with data from existing bookmark
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
existing_bookmark.title, page.get_by_label("Title").input_value()
|
existing_bookmark.title, page.get_by_label("Title").input_value()
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
existing_bookmark.description,
|
existing_bookmark.description,
|
||||||
page.get_by_label("Description").input_value(),
|
page.get_by_label("Description").input_value(),
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
existing_bookmark.notes, page.get_by_label("Notes").input_value()
|
existing_bookmark.notes, page.get_by_label("Notes").input_value()
|
||||||
)
|
)
|
||||||
self.assertEqual(tag_names, page.get_by_label("Tags").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())
|
self.assertTrue(tag_names, page.get_by_label("Mark as unread").is_checked())
|
||||||
|
|
||||||
# Enter non-bookmarked URL
|
# Enter non-bookmarked URL
|
||||||
page.get_by_label("URL").fill("https://example.com/unknown")
|
page.get_by_label("URL").fill("https://example.com/unknown")
|
||||||
# Already bookmarked hint should be hidden
|
# Already bookmarked hint should be hidden
|
||||||
page.get_by_text("This URL is already bookmarked.").wait_for(
|
page.get_by_text("This URL is already bookmarked.").wait_for(
|
||||||
state="hidden", timeout=2000
|
state="hidden", timeout=2000
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_enter_url_of_existing_bookmark_should_show_notes(self):
|
def test_enter_url_of_existing_bookmark_should_show_notes(self):
|
||||||
bookmark = self.setup_bookmark(
|
bookmark = self.setup_bookmark(
|
||||||
notes="Existing notes", description="Existing description"
|
notes="Existing notes", description="Existing description"
|
||||||
)
|
)
|
||||||
|
|
||||||
with sync_playwright() as p:
|
page = self.open(reverse("linkding:bookmarks.new"))
|
||||||
page = self.open(reverse("linkding:bookmarks.new"), p)
|
|
||||||
|
|
||||||
details = page.locator("details.notes")
|
details = page.locator("details.notes")
|
||||||
expect(details).not_to_have_attribute("open", value="")
|
expect(details).not_to_have_attribute("open", value="")
|
||||||
|
|
||||||
page.get_by_label("URL").fill(bookmark.url)
|
page.get_by_label("URL").fill(bookmark.url)
|
||||||
expect(details).to_have_attribute("open", value="")
|
expect(details).to_have_attribute("open", value="")
|
||||||
|
|
||||||
def test_create_should_preview_auto_tags(self):
|
def test_create_should_preview_auto_tags(self):
|
||||||
profile = self.get_or_create_test_user().profile
|
profile = self.get_or_create_test_user().profile
|
||||||
profile.auto_tagging_rules = "github.com dev github"
|
profile.auto_tagging_rules = "github.com dev github"
|
||||||
profile.save()
|
profile.save()
|
||||||
|
|
||||||
with sync_playwright() as p:
|
# Open page with URL that should have auto tags
|
||||||
# Open page with URL that should have auto tags
|
url = (
|
||||||
url = (
|
reverse("linkding:bookmarks.new")
|
||||||
reverse("linkding:bookmarks.new")
|
+ "?url=https%3A%2F%2Fgithub.com%2Fsissbruecker%2Flinkding"
|
||||||
+ "?url=https%3A%2F%2Fgithub.com%2Fsissbruecker%2Flinkding"
|
)
|
||||||
)
|
page = self.open(url)
|
||||||
page = self.open(url, p)
|
|
||||||
|
|
||||||
auto_tags_hint = page.locator(".form-input-hint.auto-tags")
|
auto_tags_hint = page.locator(".form-input-hint.auto-tags")
|
||||||
expect(auto_tags_hint).to_be_visible()
|
expect(auto_tags_hint).to_be_visible()
|
||||||
expect(auto_tags_hint).to_have_text("Auto tags: dev github")
|
expect(auto_tags_hint).to_have_text("Auto tags: dev github")
|
||||||
|
|
||||||
# Change to URL without auto tags
|
# Change to URL without auto tags
|
||||||
page.get_by_label("URL").fill("https://example.com")
|
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):
|
def test_clear_buttons_only_shown_when_fields_have_content(self):
|
||||||
with sync_playwright() as p:
|
page = self.open(reverse("linkding:bookmarks.new"))
|
||||||
page = self.open(reverse("linkding:bookmarks.new"), p)
|
|
||||||
|
|
||||||
title_field = page.get_by_label("Title")
|
title_field = page.get_by_label("Title")
|
||||||
title_clear_button = page.locator("ld-clear-button[data-for='id_title']")
|
title_clear_button = page.locator("ld-clear-button[data-for='id_title']")
|
||||||
description_field = page.get_by_label("Description")
|
description_field = page.get_by_label("Description")
|
||||||
description_clear_button = page.locator(
|
description_clear_button = page.locator(
|
||||||
"ld-clear-button[data-for='id_description']"
|
"ld-clear-button[data-for='id_description']"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Initially, clear buttons should be hidden because fields are empty
|
# Initially, clear buttons should be hidden because fields are empty
|
||||||
expect(title_clear_button).to_be_hidden()
|
expect(title_clear_button).to_be_hidden()
|
||||||
expect(description_clear_button).to_be_hidden()
|
expect(description_clear_button).to_be_hidden()
|
||||||
|
|
||||||
# Add content to title field, its clear button should become visible
|
# Add content to title field, its clear button should become visible
|
||||||
title_field.fill("Test title")
|
title_field.fill("Test title")
|
||||||
expect(title_clear_button).to_be_visible()
|
expect(title_clear_button).to_be_visible()
|
||||||
|
|
||||||
# Add content to description field, its clear button should become visible
|
# Add content to description field, its clear button should become visible
|
||||||
description_field.fill("Test description")
|
description_field.fill("Test description")
|
||||||
expect(description_clear_button).to_be_visible()
|
expect(description_clear_button).to_be_visible()
|
||||||
|
|
||||||
# Clear title field, its clear button should be hidden again
|
# Clear title field, its clear button should be hidden again
|
||||||
title_field.fill("")
|
title_field.fill("")
|
||||||
expect(title_clear_button).to_be_hidden()
|
expect(title_clear_button).to_be_hidden()
|
||||||
|
|
||||||
# Clear description field, its clear button should be hidden again
|
# Clear description field, its clear button should be hidden again
|
||||||
description_field.fill("")
|
description_field.fill("")
|
||||||
expect(description_clear_button).to_be_hidden()
|
expect(description_clear_button).to_be_hidden()
|
||||||
|
|
||||||
def test_refresh_button_only_shown_for_existing_bookmarks(self):
|
def test_refresh_button_only_shown_for_existing_bookmarks(self):
|
||||||
existing_bookmark = self.setup_bookmark(
|
existing_bookmark = self.setup_bookmark(
|
||||||
title="Existing title", description="Existing description"
|
title="Existing title", description="Existing description"
|
||||||
)
|
)
|
||||||
|
|
||||||
with sync_playwright() as p:
|
page = self.open(reverse("linkding:bookmarks.new"))
|
||||||
page = self.open(reverse("linkding:bookmarks.new"), p)
|
refresh_button = page.locator("#refresh-button")
|
||||||
refresh_button = page.locator("#refresh-button")
|
|
||||||
|
|
||||||
# Initially, refresh button should be hidden
|
# Initially, refresh button should be hidden
|
||||||
expect(refresh_button).to_be_hidden()
|
expect(refresh_button).to_be_hidden()
|
||||||
|
|
||||||
# Enter a URL that is not bookmarked yet
|
# Enter a URL that is not bookmarked yet
|
||||||
page.get_by_label("URL").fill("https://example.com/not-bookmarked")
|
page.get_by_label("URL").fill("https://example.com/not-bookmarked")
|
||||||
page.wait_for_timeout(timeout=1000)
|
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
|
# Enter a URL that is already bookmarked
|
||||||
page.get_by_label("URL").fill(existing_bookmark.url)
|
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
|
# Change back to non-bookmarked URL
|
||||||
page.get_by_label("URL").fill("https://example.com/another-not-bookmarked")
|
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):
|
def test_refresh_from_website_button_updates_title_and_description(self):
|
||||||
existing_bookmark = self.setup_bookmark(
|
existing_bookmark = self.setup_bookmark(
|
||||||
title="Existing title", description="Existing description"
|
title="Existing title", description="Existing description"
|
||||||
)
|
)
|
||||||
|
|
||||||
with sync_playwright() as p:
|
page = self.open(reverse("linkding:bookmarks.new"))
|
||||||
page = self.open(reverse("linkding:bookmarks.new"), p)
|
url_field = page.get_by_label("URL")
|
||||||
url_field = page.get_by_label("URL")
|
title_field = page.get_by_label("Title")
|
||||||
title_field = page.get_by_label("Title")
|
description_field = page.get_by_label("Description")
|
||||||
description_field = page.get_by_label("Description")
|
refresh_button = page.locator("#refresh-button")
|
||||||
refresh_button = page.locator("#refresh-button")
|
|
||||||
|
|
||||||
# Enter the URL of the existing bookmark to make refresh button visible
|
# Enter the URL of the existing bookmark to make refresh button visible
|
||||||
url_field.fill(existing_bookmark.url)
|
url_field.fill(existing_bookmark.url)
|
||||||
|
|
||||||
# Wait for metadata to be loaded and for refresh button to be visible
|
# Wait for metadata to be loaded and for refresh button to be visible
|
||||||
expect(refresh_button).to_be_visible()
|
expect(refresh_button).to_be_visible()
|
||||||
expect(title_field).to_have_value("Existing title")
|
expect(title_field).to_have_value("Existing title")
|
||||||
expect(description_field).to_have_value("Existing description")
|
expect(description_field).to_have_value("Existing description")
|
||||||
|
|
||||||
# Update the mock to return different metadata when refresh is requested
|
# Update the mock to return different metadata when refresh is requested
|
||||||
self.website_loader_mock.reset_mock()
|
self.website_loader_mock.reset_mock()
|
||||||
self.website_loader_mock.return_value = website_loader.WebsiteMetadata(
|
self.website_loader_mock.return_value = website_loader.WebsiteMetadata(
|
||||||
url="https://example.com",
|
url="https://example.com",
|
||||||
title="Updated Example Domain",
|
title="Updated Example Domain",
|
||||||
description="This is a refreshed description for the example domain.",
|
description="This is a refreshed description for the example domain.",
|
||||||
preview_image=None,
|
preview_image=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Click the refresh button
|
# Click the refresh button
|
||||||
refresh_button.click()
|
refresh_button.click()
|
||||||
|
|
||||||
# Verify that title and description have been updated with new values
|
# Verify that title and description have been updated with new values
|
||||||
expect(title_field).to_have_value("Updated Example Domain")
|
expect(title_field).to_have_value("Updated Example Domain")
|
||||||
expect(description_field).to_have_value(
|
expect(description_field).to_have_value(
|
||||||
"This is a refreshed description for the example domain."
|
"This is a refreshed description for the example domain."
|
||||||
)
|
)
|
||||||
|
|
||||||
# Verify that the fields are visually marked as modified
|
# Verify that the fields are visually marked as modified
|
||||||
expect(title_field).to_have_class("form-input modified")
|
expect(title_field).to_have_class("form-input modified")
|
||||||
expect(description_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(
|
def test_refresh_from_website_button_does_not_modify_fields_if_metadata_is_same(
|
||||||
self,
|
self,
|
||||||
@@ -277,59 +266,57 @@ class BookmarkFormE2ETestCase(LinkdingE2ETestCase):
|
|||||||
title="Existing title", description="Existing description"
|
title="Existing title", description="Existing description"
|
||||||
)
|
)
|
||||||
|
|
||||||
with sync_playwright() as p:
|
page = self.open(reverse("linkding:bookmarks.new"))
|
||||||
page = self.open(reverse("linkding:bookmarks.new"), p)
|
url_field = page.get_by_label("URL")
|
||||||
url_field = page.get_by_label("URL")
|
title_field = page.get_by_label("Title")
|
||||||
title_field = page.get_by_label("Title")
|
description_field = page.get_by_label("Description")
|
||||||
description_field = page.get_by_label("Description")
|
refresh_button = page.locator("#refresh-button")
|
||||||
refresh_button = page.locator("#refresh-button")
|
|
||||||
|
|
||||||
# Enter the URL of the existing bookmark to make refresh button visible
|
# Enter the URL of the existing bookmark to make refresh button visible
|
||||||
url_field.fill(existing_bookmark.url)
|
url_field.fill(existing_bookmark.url)
|
||||||
|
|
||||||
# Wait for metadata to be loaded and for refresh button to be visible
|
# Wait for metadata to be loaded and for refresh button to be visible
|
||||||
expect(refresh_button).to_be_visible()
|
expect(refresh_button).to_be_visible()
|
||||||
expect(title_field).to_have_value("Existing title")
|
expect(title_field).to_have_value("Existing title")
|
||||||
expect(description_field).to_have_value("Existing description")
|
expect(description_field).to_have_value("Existing description")
|
||||||
|
|
||||||
# Update the mock to return same metadata when refresh is requested
|
# Update the mock to return same metadata when refresh is requested
|
||||||
self.website_loader_mock.reset_mock()
|
self.website_loader_mock.reset_mock()
|
||||||
self.website_loader_mock.return_value = website_loader.WebsiteMetadata(
|
self.website_loader_mock.return_value = website_loader.WebsiteMetadata(
|
||||||
url="https://example.com",
|
url="https://example.com",
|
||||||
title="Existing title",
|
title="Existing title",
|
||||||
description="Existing description",
|
description="Existing description",
|
||||||
preview_image=None,
|
preview_image=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Click the refresh button
|
# Click the refresh button
|
||||||
refresh_button.click()
|
refresh_button.click()
|
||||||
page.wait_for_timeout(timeout=1000)
|
page.wait_for_timeout(timeout=1000)
|
||||||
|
|
||||||
# Verify that title and description values are still the same
|
# Verify that title and description values are still the same
|
||||||
expect(title_field).to_have_value("Existing title")
|
expect(title_field).to_have_value("Existing title")
|
||||||
expect(description_field).to_have_value("Existing description")
|
expect(description_field).to_have_value("Existing description")
|
||||||
|
|
||||||
# Verify that the fields are NOT visually marked as modified
|
# Verify that the fields are NOT visually marked as modified
|
||||||
expect(title_field).to_have_class("form-input")
|
expect(title_field).to_have_class("form-input")
|
||||||
expect(description_field).to_have_class("form-input")
|
expect(description_field).to_have_class("form-input")
|
||||||
|
|
||||||
def test_ctrl_enter_submits_form_from_description(self):
|
def test_ctrl_enter_submits_form_from_description(self):
|
||||||
with sync_playwright() as p:
|
page = self.open(reverse("linkding:bookmarks.new"))
|
||||||
page = self.open(reverse("linkding:bookmarks.new"), p)
|
url_field = page.get_by_label("URL")
|
||||||
url_field = page.get_by_label("URL")
|
description_field = page.get_by_label("Description")
|
||||||
description_field = page.get_by_label("Description")
|
|
||||||
|
|
||||||
url_field.fill("https://example.com")
|
url_field.fill("https://example.com")
|
||||||
description_field.fill("Test description")
|
description_field.fill("Test description")
|
||||||
description_field.focus()
|
description_field.focus()
|
||||||
|
|
||||||
# Press Ctrl+Enter to submit form
|
# Press Ctrl+Enter to submit form
|
||||||
description_field.press("Control+Enter")
|
description_field.press("Control+Enter")
|
||||||
|
|
||||||
# Should navigate away from new bookmark page after successful submission
|
# Should navigate away from new bookmark page after successful submission
|
||||||
expect(page).not_to_have_url(
|
expect(page).not_to_have_url(
|
||||||
self.live_server_url + reverse("linkding:bookmarks.new")
|
self.live_server_url + reverse("linkding:bookmarks.new")
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(1, Bookmark.objects.count())
|
self.assertEqual(1, Bookmark.objects.count())
|
||||||
bookmark = Bookmark.objects.first()
|
bookmark = Bookmark.objects.first()
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from django.urls import reverse
|
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.tests_e2e.helpers import LinkdingE2ETestCase
|
||||||
from bookmarks.models import UserProfile
|
from bookmarks.models import UserProfile
|
||||||
@@ -7,54 +7,51 @@ from bookmarks.models import UserProfile
|
|||||||
|
|
||||||
class SettingsGeneralE2ETestCase(LinkdingE2ETestCase):
|
class SettingsGeneralE2ETestCase(LinkdingE2ETestCase):
|
||||||
def test_should_only_enable_public_sharing_if_sharing_is_enabled(self):
|
def test_should_only_enable_public_sharing_if_sharing_is_enabled(self):
|
||||||
with sync_playwright() as p:
|
self.open(reverse("linkding:settings.general"))
|
||||||
browser = self.setup_browser(p)
|
|
||||||
page = browser.new_page()
|
|
||||||
page.goto(self.live_server_url + reverse("linkding:settings.general"))
|
|
||||||
|
|
||||||
enable_sharing = page.get_by_label("Enable bookmark sharing")
|
enable_sharing = self.page.get_by_label("Enable bookmark sharing")
|
||||||
enable_sharing_label = page.get_by_text("Enable bookmark sharing")
|
enable_sharing_label = self.page.get_by_text("Enable bookmark sharing")
|
||||||
enable_public_sharing = page.get_by_label("Enable public bookmark sharing")
|
enable_public_sharing = self.page.get_by_label("Enable public bookmark sharing")
|
||||||
enable_public_sharing_label = page.get_by_text(
|
enable_public_sharing_label = self.page.get_by_text(
|
||||||
"Enable public bookmark sharing"
|
"Enable public bookmark sharing"
|
||||||
)
|
)
|
||||||
default_mark_shared = page.get_by_label(
|
default_mark_shared = self.page.get_by_label(
|
||||||
"Create bookmarks as shared by default"
|
"Create bookmarks as shared by default"
|
||||||
)
|
)
|
||||||
default_mark_shared_label = page.get_by_text(
|
default_mark_shared_label = self.page.get_by_text(
|
||||||
"Create bookmarks as shared by default"
|
"Create bookmarks as shared by default"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Public sharing and default shared are disabled by default
|
# Public sharing and default shared are disabled by default
|
||||||
expect(enable_sharing).not_to_be_checked()
|
expect(enable_sharing).not_to_be_checked()
|
||||||
expect(enable_public_sharing).not_to_be_checked()
|
expect(enable_public_sharing).not_to_be_checked()
|
||||||
expect(enable_public_sharing).to_be_disabled()
|
expect(enable_public_sharing).to_be_disabled()
|
||||||
expect(default_mark_shared).not_to_be_checked()
|
expect(default_mark_shared).not_to_be_checked()
|
||||||
expect(default_mark_shared).to_be_disabled()
|
expect(default_mark_shared).to_be_disabled()
|
||||||
|
|
||||||
# Enable sharing
|
# Enable sharing
|
||||||
enable_sharing_label.click()
|
enable_sharing_label.click()
|
||||||
expect(enable_sharing).to_be_checked()
|
expect(enable_sharing).to_be_checked()
|
||||||
expect(enable_public_sharing).not_to_be_checked()
|
expect(enable_public_sharing).not_to_be_checked()
|
||||||
expect(enable_public_sharing).to_be_enabled()
|
expect(enable_public_sharing).to_be_enabled()
|
||||||
expect(default_mark_shared).not_to_be_checked()
|
expect(default_mark_shared).not_to_be_checked()
|
||||||
expect(default_mark_shared).to_be_enabled()
|
expect(default_mark_shared).to_be_enabled()
|
||||||
|
|
||||||
# Enable public sharing and default shared
|
# Enable public sharing and default shared
|
||||||
enable_public_sharing_label.click()
|
enable_public_sharing_label.click()
|
||||||
default_mark_shared_label.click()
|
default_mark_shared_label.click()
|
||||||
expect(enable_public_sharing).to_be_checked()
|
expect(enable_public_sharing).to_be_checked()
|
||||||
expect(enable_public_sharing).to_be_enabled()
|
expect(enable_public_sharing).to_be_enabled()
|
||||||
expect(default_mark_shared).to_be_checked()
|
expect(default_mark_shared).to_be_checked()
|
||||||
expect(default_mark_shared).to_be_enabled()
|
expect(default_mark_shared).to_be_enabled()
|
||||||
|
|
||||||
# Disable sharing
|
# Disable sharing
|
||||||
enable_sharing_label.click()
|
enable_sharing_label.click()
|
||||||
expect(enable_sharing).not_to_be_checked()
|
expect(enable_sharing).not_to_be_checked()
|
||||||
expect(enable_public_sharing).not_to_be_checked()
|
expect(enable_public_sharing).not_to_be_checked()
|
||||||
expect(enable_public_sharing).to_be_disabled()
|
expect(enable_public_sharing).to_be_disabled()
|
||||||
expect(default_mark_shared).not_to_be_checked()
|
expect(default_mark_shared).not_to_be_checked()
|
||||||
expect(default_mark_shared).to_be_disabled()
|
expect(default_mark_shared).to_be_disabled()
|
||||||
|
|
||||||
def test_should_not_show_bookmark_description_max_lines_when_display_inline(self):
|
def test_should_not_show_bookmark_description_max_lines_when_display_inline(self):
|
||||||
profile = self.get_or_create_test_user().profile
|
profile = self.get_or_create_test_user().profile
|
||||||
@@ -63,13 +60,10 @@ class SettingsGeneralE2ETestCase(LinkdingE2ETestCase):
|
|||||||
)
|
)
|
||||||
profile.save()
|
profile.save()
|
||||||
|
|
||||||
with sync_playwright() as p:
|
self.open(reverse("linkding:settings.general"))
|
||||||
browser = self.setup_browser(p)
|
|
||||||
page = browser.new_page()
|
|
||||||
page.goto(self.live_server_url + reverse("linkding:settings.general"))
|
|
||||||
|
|
||||||
max_lines = page.get_by_label("Bookmark description max lines")
|
max_lines = self.page.get_by_label("Bookmark description max lines")
|
||||||
expect(max_lines).to_be_hidden()
|
expect(max_lines).to_be_hidden()
|
||||||
|
|
||||||
def test_should_show_bookmark_description_max_lines_when_display_separate(self):
|
def test_should_show_bookmark_description_max_lines_when_display_separate(self):
|
||||||
profile = self.get_or_create_test_user().profile
|
profile = self.get_or_create_test_user().profile
|
||||||
@@ -78,26 +72,20 @@ class SettingsGeneralE2ETestCase(LinkdingE2ETestCase):
|
|||||||
)
|
)
|
||||||
profile.save()
|
profile.save()
|
||||||
|
|
||||||
with sync_playwright() as p:
|
self.open(reverse("linkding:settings.general"))
|
||||||
browser = self.setup_browser(p)
|
|
||||||
page = browser.new_page()
|
|
||||||
page.goto(self.live_server_url + reverse("linkding:settings.general"))
|
|
||||||
|
|
||||||
max_lines = page.get_by_label("Bookmark description max lines")
|
max_lines = self.page.get_by_label("Bookmark description max lines")
|
||||||
expect(max_lines).to_be_visible()
|
expect(max_lines).to_be_visible()
|
||||||
|
|
||||||
def test_should_update_bookmark_description_max_lines_when_changing_display(self):
|
def test_should_update_bookmark_description_max_lines_when_changing_display(self):
|
||||||
with sync_playwright() as p:
|
self.open(reverse("linkding:settings.general"))
|
||||||
browser = self.setup_browser(p)
|
|
||||||
page = browser.new_page()
|
|
||||||
page.goto(self.live_server_url + reverse("linkding:settings.general"))
|
|
||||||
|
|
||||||
max_lines = page.get_by_label("Bookmark description max lines")
|
max_lines = self.page.get_by_label("Bookmark description max lines")
|
||||||
expect(max_lines).to_be_hidden()
|
expect(max_lines).to_be_hidden()
|
||||||
|
|
||||||
display = page.get_by_label("Bookmark description", exact=True)
|
display = self.page.get_by_label("Bookmark description", exact=True)
|
||||||
display.select_option("separate")
|
display.select_option("separate")
|
||||||
expect(max_lines).to_be_visible()
|
expect(max_lines).to_be_visible()
|
||||||
|
|
||||||
display.select_option("inline")
|
display.select_option("inline")
|
||||||
expect(max_lines).to_be_hidden()
|
expect(max_lines).to_be_hidden()
|
||||||
|
|||||||
@@ -1,67 +1,65 @@
|
|||||||
from django.urls import reverse
|
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.tests_e2e.helpers import LinkdingE2ETestCase
|
||||||
|
|
||||||
|
|
||||||
class SettingsIntegrationsE2ETestCase(LinkdingE2ETestCase):
|
class SettingsIntegrationsE2ETestCase(LinkdingE2ETestCase):
|
||||||
def test_create_api_token(self):
|
def test_create_api_token(self):
|
||||||
with sync_playwright() as p:
|
self.open(reverse("linkding:settings.integrations"))
|
||||||
self.open(reverse("linkding:settings.integrations"), p)
|
|
||||||
|
|
||||||
# Click create API token button
|
# Click create API token button
|
||||||
self.page.get_by_text("Create API token").click()
|
self.page.get_by_text("Create API token").click()
|
||||||
|
|
||||||
# Wait for modal to appear
|
# Wait for modal to appear
|
||||||
modal = self.page.locator(".modal")
|
modal = self.page.locator(".modal")
|
||||||
expect(modal).to_be_visible()
|
expect(modal).to_be_visible()
|
||||||
|
|
||||||
# Enter custom token name
|
# Enter custom token name
|
||||||
token_name_input = modal.locator("#token-name")
|
token_name_input = modal.locator("#token-name")
|
||||||
token_name_input.fill("")
|
token_name_input.fill("")
|
||||||
token_name_input.fill("My Test Token")
|
token_name_input.fill("My Test Token")
|
||||||
|
|
||||||
# Confirm the dialog
|
# Confirm the dialog
|
||||||
modal.page.get_by_role("button", name="Create Token").click()
|
modal.page.get_by_role("button", name="Create Token").click()
|
||||||
|
|
||||||
# Verify the API token key is shown in the input
|
# Verify the API token key is shown in the input
|
||||||
new_token_input = self.page.locator("#new-token-key")
|
new_token_input = self.page.locator("#new-token-key")
|
||||||
expect(new_token_input).to_be_visible()
|
expect(new_token_input).to_be_visible()
|
||||||
token_value = new_token_input.input_value()
|
token_value = new_token_input.input_value()
|
||||||
self.assertTrue(len(token_value) > 0)
|
self.assertTrue(len(token_value) > 0)
|
||||||
|
|
||||||
# Verify the API token is now listed in the table
|
# Verify the API token is now listed in the table
|
||||||
token_table = self.page.locator("table.crud-table")
|
token_table = self.page.locator("table.crud-table")
|
||||||
expect(token_table).to_be_visible()
|
expect(token_table).to_be_visible()
|
||||||
expect(token_table.get_by_text("My Test Token")).to_be_visible()
|
expect(token_table.get_by_text("My Test Token")).to_be_visible()
|
||||||
|
|
||||||
# Verify the dialog is gone
|
# Verify the dialog is gone
|
||||||
expect(modal).to_be_hidden()
|
expect(modal).to_be_hidden()
|
||||||
|
|
||||||
# Reload the page to verify the API token key is only shown once
|
# Reload the page to verify the API token key is only shown once
|
||||||
self.page.reload()
|
self.page.reload()
|
||||||
|
|
||||||
# Token key input should no longer be visible
|
# Token key input should no longer be visible
|
||||||
expect(new_token_input).not_to_be_visible()
|
expect(new_token_input).not_to_be_visible()
|
||||||
|
|
||||||
# But the token should still be listed in the table
|
# But the token should still be listed in the table
|
||||||
expect(token_table.get_by_text("My Test Token")).to_be_visible()
|
expect(token_table.get_by_text("My Test Token")).to_be_visible()
|
||||||
|
|
||||||
def test_delete_api_token(self):
|
def test_delete_api_token(self):
|
||||||
self.setup_api_token(name="Token To Delete")
|
self.setup_api_token(name="Token To Delete")
|
||||||
|
|
||||||
with sync_playwright() as p:
|
self.open(reverse("linkding:settings.integrations"))
|
||||||
self.open(reverse("linkding:settings.integrations"), p)
|
|
||||||
|
|
||||||
token_table = self.page.locator("table.crud-table")
|
token_table = self.page.locator("table.crud-table")
|
||||||
expect(token_table.get_by_text("Token To Delete")).to_be_visible()
|
expect(token_table.get_by_text("Token To Delete")).to_be_visible()
|
||||||
|
|
||||||
# Click delete button for the token
|
# Click delete button for the token
|
||||||
token_row = token_table.locator("tr").filter(has_text="Token To Delete")
|
token_row = token_table.locator("tr").filter(has_text="Token To Delete")
|
||||||
token_row.get_by_role("button", name="Delete").click()
|
token_row.get_by_role("button", name="Delete").click()
|
||||||
|
|
||||||
# Confirm deletion
|
# Confirm deletion
|
||||||
self.locate_confirm_dialog().get_by_text("Confirm").click()
|
self.locate_confirm_dialog().get_by_text("Confirm").click()
|
||||||
|
|
||||||
# Verify the token is removed from the table
|
# Verify the token is removed from the table
|
||||||
expect(token_table.get_by_text("Token To Delete")).not_to_be_visible()
|
expect(token_table.get_by_text("Token To Delete")).not_to_be_visible()
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from django.urls import reverse
|
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.models import Tag
|
||||||
from bookmarks.tests_e2e.helpers import LinkdingE2ETestCase
|
from bookmarks.tests_e2e.helpers import LinkdingE2ETestCase
|
||||||
@@ -25,30 +25,29 @@ class TagManagementE2ETestCase(LinkdingE2ETestCase):
|
|||||||
expect(success_message).to_contain_text(text)
|
expect(success_message).to_contain_text(text)
|
||||||
|
|
||||||
def test_create_tag(self):
|
def test_create_tag(self):
|
||||||
with sync_playwright() as p:
|
self.open(reverse("linkding:tags.index"))
|
||||||
self.open(reverse("linkding:tags.index"), p)
|
|
||||||
|
|
||||||
# Click the Create Tag button to open the modal
|
# Click the Create Tag button to open the modal
|
||||||
self.page.get_by_text("Create Tag").click()
|
self.page.get_by_text("Create Tag").click()
|
||||||
|
|
||||||
modal = self.locate_tag_modal()
|
modal = self.locate_tag_modal()
|
||||||
|
|
||||||
# Fill in a tag name
|
# Fill in a tag name
|
||||||
name_input = modal.get_by_label("Name")
|
name_input = modal.get_by_label("Name")
|
||||||
name_input.fill("test-tag")
|
name_input.fill("test-tag")
|
||||||
|
|
||||||
# Submit the form
|
# Submit the form
|
||||||
modal.get_by_text("Save").click()
|
modal.get_by_text("Save").click()
|
||||||
|
|
||||||
# Verify modal is closed and we're back on the tags page
|
# Verify modal is closed and we're back on the tags page
|
||||||
expect(modal).not_to_be_visible()
|
expect(modal).not_to_be_visible()
|
||||||
|
|
||||||
# Verify the success message is shown
|
# Verify the success message is shown
|
||||||
self.verify_success_message('Tag "test-tag" created successfully.')
|
self.verify_success_message('Tag "test-tag" created successfully.')
|
||||||
|
|
||||||
# Verify the new tag is shown in the list
|
# Verify the new tag is shown in the list
|
||||||
tag_row = self.locate_tag_row("test-tag")
|
tag_row = self.locate_tag_row("test-tag")
|
||||||
expect(tag_row).to_be_visible()
|
expect(tag_row).to_be_visible()
|
||||||
|
|
||||||
# Verify the tag was actually created in the database
|
# Verify the tag was actually created in the database
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
@@ -60,31 +59,30 @@ class TagManagementE2ETestCase(LinkdingE2ETestCase):
|
|||||||
def test_create_tag_validation_error(self):
|
def test_create_tag_validation_error(self):
|
||||||
existing_tag = self.setup_tag(name="existing-tag")
|
existing_tag = self.setup_tag(name="existing-tag")
|
||||||
|
|
||||||
with sync_playwright() as p:
|
self.open(reverse("linkding:tags.index"))
|
||||||
self.open(reverse("linkding:tags.index"), p)
|
|
||||||
|
|
||||||
# Click the Create Tag button to open the modal
|
# Click the Create Tag button to open the modal
|
||||||
self.page.get_by_text("Create Tag").click()
|
self.page.get_by_text("Create Tag").click()
|
||||||
|
|
||||||
modal = self.locate_tag_modal()
|
modal = self.locate_tag_modal()
|
||||||
|
|
||||||
# Submit with empty value
|
# Submit with empty value
|
||||||
modal.get_by_text("Save").click()
|
modal.get_by_text("Save").click()
|
||||||
|
|
||||||
# Verify the error is shown (field is required)
|
# Verify the error is shown (field is required)
|
||||||
error_hint = modal.get_by_text("This field is required")
|
error_hint = modal.get_by_text("This field is required")
|
||||||
expect(error_hint).to_be_visible()
|
expect(error_hint).to_be_visible()
|
||||||
|
|
||||||
# Fill in the name of an existing tag
|
# Fill in the name of an existing tag
|
||||||
name_input = modal.get_by_label("Name")
|
name_input = modal.get_by_label("Name")
|
||||||
name_input.fill(existing_tag.name)
|
name_input.fill(existing_tag.name)
|
||||||
|
|
||||||
# Submit the form
|
# Submit the form
|
||||||
modal.get_by_text("Save").click()
|
modal.get_by_text("Save").click()
|
||||||
|
|
||||||
# Verify the error is shown (tag already exists)
|
# Verify the error is shown (tag already exists)
|
||||||
error_hint = modal.get_by_text('Tag "existing-tag" already exists')
|
error_hint = modal.get_by_text('Tag "existing-tag" already exists')
|
||||||
expect(error_hint).to_be_visible()
|
expect(error_hint).to_be_visible()
|
||||||
|
|
||||||
# Verify no additional tag was created
|
# Verify no additional tag was created
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
@@ -94,34 +92,33 @@ class TagManagementE2ETestCase(LinkdingE2ETestCase):
|
|||||||
def test_edit_tag(self):
|
def test_edit_tag(self):
|
||||||
tag = self.setup_tag(name="old-name")
|
tag = self.setup_tag(name="old-name")
|
||||||
|
|
||||||
with sync_playwright() as p:
|
self.open(reverse("linkding:tags.index"))
|
||||||
self.open(reverse("linkding:tags.index"), p)
|
|
||||||
|
|
||||||
# Click the Edit button for the tag
|
# Click the Edit button for the tag
|
||||||
tag_row = self.locate_tag_row(tag.name)
|
tag_row = self.locate_tag_row(tag.name)
|
||||||
tag_row.get_by_role("link", name="Edit").click()
|
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
|
# Verify the form is pre-filled with the tag name
|
||||||
name_input = modal.get_by_label("Name")
|
name_input = modal.get_by_label("Name")
|
||||||
expect(name_input).to_have_value(tag.name)
|
expect(name_input).to_have_value(tag.name)
|
||||||
|
|
||||||
# Change the tag name
|
# Change the tag name
|
||||||
name_input.fill("new-name")
|
name_input.fill("new-name")
|
||||||
|
|
||||||
# Submit the form
|
# Submit the form
|
||||||
modal.get_by_text("Save").click()
|
modal.get_by_text("Save").click()
|
||||||
|
|
||||||
# Verify modal is closed
|
# Verify modal is closed
|
||||||
expect(modal).not_to_be_visible()
|
expect(modal).not_to_be_visible()
|
||||||
|
|
||||||
# Verify the success message is shown
|
# Verify the success message is shown
|
||||||
self.verify_success_message('Tag "new-name" updated successfully.')
|
self.verify_success_message('Tag "new-name" updated successfully.')
|
||||||
|
|
||||||
# Verify the updated tag is shown in the list
|
# Verify the updated tag is shown in the list
|
||||||
expect(self.locate_tag_row("new-name")).to_be_visible()
|
expect(self.locate_tag_row("new-name")).to_be_visible()
|
||||||
expect(self.locate_tag_row("old-name")).not_to_be_visible()
|
expect(self.locate_tag_row("old-name")).not_to_be_visible()
|
||||||
|
|
||||||
# Verify the tag was updated in the database
|
# Verify the tag was updated in the database
|
||||||
tag.refresh_from_db()
|
tag.refresh_from_db()
|
||||||
@@ -131,31 +128,30 @@ class TagManagementE2ETestCase(LinkdingE2ETestCase):
|
|||||||
tag = self.setup_tag(name="tag-to-edit")
|
tag = self.setup_tag(name="tag-to-edit")
|
||||||
other_tag = self.setup_tag(name="other-tag")
|
other_tag = self.setup_tag(name="other-tag")
|
||||||
|
|
||||||
with sync_playwright() as p:
|
self.open(reverse("linkding:tags.index"))
|
||||||
self.open(reverse("linkding:tags.index"), p)
|
|
||||||
|
|
||||||
# Click the Edit button for the tag
|
# Click the Edit button for the tag
|
||||||
tag_row = self.locate_tag_row(tag.name)
|
tag_row = self.locate_tag_row(tag.name)
|
||||||
tag_row.get_by_role("link", name="Edit").click()
|
tag_row.get_by_role("link", name="Edit").click()
|
||||||
|
|
||||||
modal = self.locate_tag_modal()
|
modal = self.locate_tag_modal()
|
||||||
|
|
||||||
# Clear the name and submit
|
# Clear the name and submit
|
||||||
name_input = modal.get_by_label("Name")
|
name_input = modal.get_by_label("Name")
|
||||||
name_input.fill("")
|
name_input.fill("")
|
||||||
modal.get_by_text("Save").click()
|
modal.get_by_text("Save").click()
|
||||||
|
|
||||||
# Verify the error is shown (field is required)
|
# Verify the error is shown (field is required)
|
||||||
error_hint = modal.get_by_text("This field is required")
|
error_hint = modal.get_by_text("This field is required")
|
||||||
expect(error_hint).to_be_visible()
|
expect(error_hint).to_be_visible()
|
||||||
|
|
||||||
# Fill in the name of another existing tag
|
# Fill in the name of another existing tag
|
||||||
name_input.fill(other_tag.name)
|
name_input.fill(other_tag.name)
|
||||||
modal.get_by_text("Save").click()
|
modal.get_by_text("Save").click()
|
||||||
|
|
||||||
# Verify the error is shown (tag already exists)
|
# Verify the error is shown (tag already exists)
|
||||||
error_hint = modal.get_by_text('Tag "other-tag" already exists')
|
error_hint = modal.get_by_text('Tag "other-tag" already exists')
|
||||||
expect(error_hint).to_be_visible()
|
expect(error_hint).to_be_visible()
|
||||||
|
|
||||||
# Verify the tag was not modified
|
# Verify the tag was not modified
|
||||||
tag.refresh_from_db()
|
tag.refresh_from_db()
|
||||||
@@ -170,37 +166,36 @@ class TagManagementE2ETestCase(LinkdingE2ETestCase):
|
|||||||
bookmark1 = self.setup_bookmark(tags=[merge_tag1])
|
bookmark1 = self.setup_bookmark(tags=[merge_tag1])
|
||||||
bookmark2 = self.setup_bookmark(tags=[merge_tag2])
|
bookmark2 = self.setup_bookmark(tags=[merge_tag2])
|
||||||
|
|
||||||
with sync_playwright() as p:
|
self.open(reverse("linkding:tags.index"))
|
||||||
self.open(reverse("linkding:tags.index"), p)
|
|
||||||
|
|
||||||
# Click the Merge Tags button to open the modal
|
# Click the Merge Tags button to open the modal
|
||||||
self.page.get_by_text("Merge Tags", exact=True).click()
|
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
|
# Fill in the target tag
|
||||||
target_input = modal.get_by_label("Target tag")
|
target_input = modal.get_by_label("Target tag")
|
||||||
target_input.fill(target_tag.name)
|
target_input.fill(target_tag.name)
|
||||||
|
|
||||||
# Fill in the tags to merge
|
# Fill in the tags to merge
|
||||||
merge_input = modal.get_by_label("Tags to merge")
|
merge_input = modal.get_by_label("Tags to merge")
|
||||||
merge_input.fill(f"{merge_tag1.name} {merge_tag2.name}")
|
merge_input.fill(f"{merge_tag1.name} {merge_tag2.name}")
|
||||||
|
|
||||||
# Submit the form
|
# Submit the form
|
||||||
modal.get_by_role("button", name="Merge Tags").click()
|
modal.get_by_role("button", name="Merge Tags").click()
|
||||||
|
|
||||||
# Verify modal is closed
|
# Verify modal is closed
|
||||||
expect(modal).not_to_be_visible()
|
expect(modal).not_to_be_visible()
|
||||||
|
|
||||||
# Verify the success message is shown
|
# Verify the success message is shown
|
||||||
self.verify_success_message(
|
self.verify_success_message(
|
||||||
'Successfully merged 2 tags (merge-tag1, merge-tag2) into "target-tag".'
|
'Successfully merged 2 tags (merge-tag1, merge-tag2) into "target-tag".'
|
||||||
)
|
)
|
||||||
|
|
||||||
# Verify the merged tags are no longer in the list
|
# 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("target-tag")).to_be_visible()
|
||||||
expect(self.locate_tag_row("merge-tag1")).not_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()
|
expect(self.locate_tag_row("merge-tag2")).not_to_be_visible()
|
||||||
|
|
||||||
# Verify the merge tags were deleted
|
# Verify the merge tags were deleted
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
@@ -217,44 +212,43 @@ class TagManagementE2ETestCase(LinkdingE2ETestCase):
|
|||||||
target_tag = self.setup_tag(name="target-tag")
|
target_tag = self.setup_tag(name="target-tag")
|
||||||
merge_tag = self.setup_tag(name="merge-tag")
|
merge_tag = self.setup_tag(name="merge-tag")
|
||||||
|
|
||||||
with sync_playwright() as p:
|
self.open(reverse("linkding:tags.index"))
|
||||||
self.open(reverse("linkding:tags.index"), p)
|
|
||||||
|
|
||||||
# Click the Merge Tags button to open the modal
|
# Click the Merge Tags button to open the modal
|
||||||
self.page.get_by_text("Merge Tags", exact=True).click()
|
self.page.get_by_text("Merge Tags", exact=True).click()
|
||||||
|
|
||||||
modal = self.locate_merge_modal()
|
modal = self.locate_merge_modal()
|
||||||
|
|
||||||
# Submit with empty values
|
# Submit with empty values
|
||||||
modal.get_by_role("button", name="Merge Tags").click()
|
modal.get_by_role("button", name="Merge Tags").click()
|
||||||
|
|
||||||
# Verify the errors are shown
|
# Verify the errors are shown
|
||||||
expect(modal.get_by_text("This field is required").first).to_be_visible()
|
expect(modal.get_by_text("This field is required").first).to_be_visible()
|
||||||
|
|
||||||
# Fill in non-existent target tag
|
# Fill in non-existent target tag
|
||||||
target_input = modal.get_by_label("Target tag")
|
target_input = modal.get_by_label("Target tag")
|
||||||
target_input.fill("nonexistent-tag")
|
target_input.fill("nonexistent-tag")
|
||||||
|
|
||||||
merge_input = modal.get_by_label("Tags to merge")
|
merge_input = modal.get_by_label("Tags to merge")
|
||||||
merge_input.fill(merge_tag.name)
|
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
|
# Verify error for non-existent target tag
|
||||||
expect(
|
expect(
|
||||||
modal.get_by_text('Tag "nonexistent-tag" does not exist')
|
modal.get_by_text('Tag "nonexistent-tag" does not exist')
|
||||||
).to_be_visible()
|
).to_be_visible()
|
||||||
|
|
||||||
# Fill in valid target but target tag in merge tags
|
# Fill in valid target but target tag in merge tags
|
||||||
target_input.fill(target_tag.name)
|
target_input.fill(target_tag.name)
|
||||||
merge_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
|
# Verify error for target tag in merge tags
|
||||||
expect(
|
expect(
|
||||||
modal.get_by_text("The target tag cannot be selected for merging")
|
modal.get_by_text("The target tag cannot be selected for merging")
|
||||||
).to_be_visible()
|
).to_be_visible()
|
||||||
|
|
||||||
# Verify no tags were deleted
|
# Verify no tags were deleted
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
|
|||||||
@@ -1,18 +1,66 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
from django.contrib.staticfiles.testing import LiveServerTestCase
|
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 playwright.sync_api import expect
|
||||||
|
|
||||||
from bookmarks.tests.helpers import BookmarkFactoryMixin
|
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):
|
class LinkdingE2ETestCase(LiveServerTestCase, BookmarkFactoryMixin):
|
||||||
def setUp(self) -> None:
|
def setUp(self) -> None:
|
||||||
self.client.force_login(self.get_or_create_test_user())
|
self.client.force_login(self.get_or_create_test_user())
|
||||||
self.cookie = self.client.cookies["sessionid"]
|
self.cookie = self.client.cookies["sessionid"]
|
||||||
|
self.playwright = None
|
||||||
|
self.browser = None
|
||||||
|
self.context = None
|
||||||
|
self.page = None
|
||||||
|
|
||||||
def setup_browser(self, playwright) -> BrowserContext:
|
def tearDown(self) -> None:
|
||||||
browser = playwright.chromium.launch(headless=True)
|
if self.page and self._test_has_failed():
|
||||||
context = browser.new_context()
|
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(
|
context.add_cookies(
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
@@ -25,14 +73,20 @@ class LinkdingE2ETestCase(LiveServerTestCase, BookmarkFactoryMixin):
|
|||||||
)
|
)
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def open(self, url: str, playwright: Playwright) -> Page:
|
def open(self, url: str) -> Page:
|
||||||
browser = self.setup_browser(playwright)
|
self.context = self.setup_browser()
|
||||||
self.page = browser.new_page()
|
self.page = self.context.new_page()
|
||||||
|
self.page.on("pageerror", self.on_page_error)
|
||||||
self.page.goto(self.live_server_url + url)
|
self.page.goto(self.live_server_url + url)
|
||||||
self.page.on("load", self.on_load)
|
self.page.on("load", self.on_load)
|
||||||
self.num_loads = 0
|
self.num_loads = 0
|
||||||
return self.page
|
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):
|
def on_load(self):
|
||||||
self.num_loads += 1
|
self.num_loads += 1
|
||||||
|
|
||||||
|
|||||||
@@ -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=<client-id>
|
|
||||||
@@ -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"
|
|
||||||
@@ -23,7 +23,8 @@ export LD_DB_ENGINE=postgres
|
|||||||
export LD_DB_USER=linkding
|
export LD_DB_USER=linkding
|
||||||
export LD_DB_PASSWORD=linkding
|
export LD_DB_PASSWORD=linkding
|
||||||
|
|
||||||
./scripts/test.sh
|
make test
|
||||||
|
make e2e
|
||||||
|
|
||||||
# Remove postgres container
|
# Remove postgres container
|
||||||
docker rm -f linkding-postgres-test || true
|
docker rm -f linkding-postgres-test || true
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
uv run manage.py test bookmarks.tests
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
./scripts/test-unit.sh
|
|
||||||
./scripts/test-e2e.sh
|
|
||||||
Reference in New Issue
Block a user