datasette/tests/test_playwright.py

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

147 lines
4.3 KiB
Python
Raw Normal View History

import socket
import subprocess
import sys
import time
import httpx
import pytest
from datasette.fixtures import write_fixture_database
def find_free_port():
with socket.socket() as sock:
sock.bind(("127.0.0.1", 0))
return sock.getsockname()[1]
def wait_for_server(process, url, timeout=10):
deadline = time.monotonic() + timeout
last_error = None
while time.monotonic() < deadline:
if process.poll() is not None:
stdout, stderr = process.communicate()
raise AssertionError(
"Datasette server exited early\n"
f"stdout:\n{stdout}\n"
f"stderr:\n{stderr}"
)
try:
response = httpx.get(url, timeout=1.0)
if response.status_code < 500:
return
last_error = f"HTTP {response.status_code}: {response.text[:200]}"
except httpx.HTTPError as ex:
last_error = repr(ex)
time.sleep(0.1)
raise AssertionError(f"Timed out waiting for {url}: {last_error}")
@pytest.fixture
def datasette_server(tmp_path):
db_path = tmp_path / "fixtures.db"
write_fixture_database(str(db_path))
port = find_free_port()
process = subprocess.Popen(
[
sys.executable,
"-m",
"datasette",
str(db_path),
"--host",
"127.0.0.1",
"--port",
str(port),
"--setting",
"num_sql_threads",
"1",
],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
)
url = f"http://127.0.0.1:{port}/"
try:
wait_for_server(process, url)
yield url
finally:
process.terminate()
try:
process.wait(timeout=5)
except subprocess.TimeoutExpired:
process.kill()
process.wait()
@pytest.mark.playwright
def test_datasette_homepage_contains_datasette(page, datasette_server):
page.goto(datasette_server)
assert "Datasette" in page.locator("body").inner_text()
@pytest.mark.playwright
def test_navigation_search_tracks_and_renders_recent_items(page, datasette_server):
page.goto(datasette_server)
result = page.evaluate("""
async () => {
await customElements.whenDefined("navigation-search");
const element = document.querySelector("navigation-search");
const key = element.recentItemsStorageKey();
localStorage.removeItem(key);
const items = Array.from({ length: 6 }, (_, index) => ({
name: `Item ${index + 1}`,
url: `/item-${index + 1}`,
type: "table",
description: "Table",
}));
items[5].name = "content: recent_datasette_releases";
items[5].display_name = "Recent Datasette releases";
for (const item of items) {
element.saveRecentItem(item);
}
const stored = JSON.parse(localStorage.getItem(key));
element.matches = [
items[5],
items[4],
{
name: "Other",
url: "/other",
type: "database",
description: "Database",
},
];
element.shadowRoot.querySelector(".search-input").value = "";
element.renderResults();
const html = element.shadowRoot.querySelector(".results-container").innerHTML;
element.clearRecentItems();
const clearedValue = localStorage.getItem(key);
element.renderResults();
const htmlAfterClear = element.shadowRoot
.querySelector(".results-container")
.innerHTML;
return { stored, html, clearedValue, htmlAfterClear };
}
""")
assert [item["url"] for item in result["stored"]] == [
"/item-6",
"/item-5",
"/item-4",
"/item-3",
"/item-2",
]
assert result["stored"][0]["display_name"] == "Recent Datasette releases"
assert "Recent" in result["html"]
assert "Recent Datasette releases" in result["html"]
assert "Item 5" in result["html"]
assert "content: recent_datasette_releases" in result["html"]
assert "Item 4" in result["html"]
assert "Item 2" in result["html"]
assert "Other" not in result["html"]
assert "Clear recent" in result["html"]
assert result["clearedValue"] is None
assert "Clear recent" not in result["htmlAfterClear"]