diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index d6782ed..c9bc208 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -1,6 +1,4 @@ -name: Playwright Tests -# https://devblogs.microsoft.com/python/announcing-playwright-for-python-reliable-end-to-end-testing-for-the-web/ -# https://github.com/marketplace/actions/run-playwright-tests +name: Site Tests on: push: @@ -11,9 +9,11 @@ on: - gh-pages workflow_dispatch: +permissions: + contents: read + jobs: test: - timeout-minutes: 30 runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -22,11 +22,8 @@ jobs: with: version: "latest" - name: Set up Python - run: uv python install 3.12 + run: uv python install 3.14 - name: Install dependencies - run: | - uv sync --extra dev - uv run playwright install --with-deps chromium + run: uv sync --dev prerelease allow - name: Run Tests - run: | - uv run python -m pytest tests/test.py::test_destination -v || echo "Some tests failed but continuing..." + run: uv run pytest -v diff --git a/pyproject.toml b/pyproject.toml index b8e532f..1449edd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,10 @@ name = "blackpythondevs-site" version = "0.1.0" description = "Black Python Devs community website" requires-python = ">=3.13" -dependencies = ["render-engine[cli]>=2026.3.4a2", "pyyaml"] +dependencies = [ + "render-engine[cli]>=2026.3.4a2", + "pyyaml", +] [project.optional-dependencies] dev = [ @@ -11,11 +14,7 @@ dev = [ "typer", "rich", "python-frontmatter", - "ephemeral_port_reserve", - "pytest-playwright", - "pytest-xprocess", - "axe-core-python==0.1.0", - "axe-playwright-python==0.1.7", + "pytest", ] [tool.render-engine.cli] diff --git a/tests/conftest.py b/tests/conftest.py index 9308183..46d107c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,15 +1,4 @@ import pytest -import ephemeral_port_reserve - - -@pytest.fixture(scope="module") -def url_port() -> tuple[str, int]: - """returns the localhost and port""" - hostname = ephemeral_port_reserve.LOCALHOST - free_port = ephemeral_port_reserve.reserve(hostname) - url = f"http://{hostname}:{free_port}" - return url, free_port - ROUTES = [ "", @@ -23,16 +12,3 @@ def url_port() -> tuple[str, int]: @pytest.fixture(params=ROUTES) def loaded_route(request): return request.param - - -PROFILES = [ - "iPhone 15", - "iPhone 15 landscape", - "iPhone 15 Plus", - "iPhone 15 Plus landscape", -] - - -@pytest.fixture(scope="session", params=PROFILES) -def loaded_profile(request): - return request.param diff --git a/tests/test.py b/tests/test.py index 35d230c..c07abb0 100644 --- a/tests/test.py +++ b/tests/test.py @@ -1,215 +1,154 @@ import pathlib -import pytest -from playwright.sync_api import expect, sync_playwright -from axe_core_python.sync_playwright import Axe -import frontmatter -from typing import Generator +import shutil import subprocess -import http.server -import socketserver -import threading -import time -import socket - - -def find_free_port(): - """Find a free port to use for the test server""" - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - s.bind(("", 0)) - s.listen(1) - port = s.getsockname()[1] - return port - - -@pytest.fixture(scope="session") -def built_site(): - """Build the site once for all tests""" - print("Building site for tests...") - result = subprocess.run( - ["uv", "run", "render-engine", "build"], # use uv - capture_output=True, - text=True, - ) - if result.returncode != 0: - pytest.fail(f"Failed to build site: {result.stderr}") - return pathlib.Path("output") - +from html.parser import HTMLParser -@pytest.fixture(scope="session") -def test_server(built_site): - """Start a simple HTTP server to serve the built site""" - port = find_free_port() - - class Handler(http.server.SimpleHTTPRequestHandler): - def __init__(self, *args, **kwargs): - super().__init__(*args, directory=str(built_site), **kwargs) +import pytest - httpd = socketserver.TCPServer(("", port), Handler) - # Start server in a thread - server_thread = threading.Thread(target=httpd.serve_forever) - server_thread.daemon = True - server_thread.start() +class HTMLMetaParser(HTMLParser): + """Simple HTML parser to extract metadata from HTML files.""" - # Wait for server to start - time.sleep(1) + def __init__(self): + super().__init__() + self.title = "" + self._in_title = False + self.lang = None + self.meta_description = None + self.links = [] + self.classes = [] - base_url = f"http://localhost:{port}" + def handle_starttag(self, tag, attrs): + attrs_dict = dict(attrs) + if tag == "html": + self.lang = attrs_dict.get("lang") + elif tag == "title": + self._in_title = True + elif tag == "meta" and attrs_dict.get("name") == "description": + self.meta_description = attrs_dict.get("content", "") + elif tag == "a": + self.links.append(attrs_dict) + if "class" in attrs_dict: + self.classes.extend(attrs_dict["class"].split()) - yield base_url + def handle_data(self, data): + if self._in_title: + self.title += data - httpd.shutdown() + def handle_endtag(self, tag): + if tag == "title": + self._in_title = False -@pytest.fixture(scope="session") -def browser_context(test_server): - """Create a browser context for all tests""" - with sync_playwright() as p: - browser = p.chromium.launch() - context = browser.new_context() - page = context.new_page() +def parse_html(path: pathlib.Path) -> HTMLMetaParser: + parser = HTMLMetaParser() + parser.feed(path.read_text()) + return parser - yield page, test_server - context.close() - browser.close() +OUTPUT_DIR = pathlib.Path("output") -def test_accessibility(browser_context): - """Run accessibility tests on the homepage""" - page, base_url = browser_context - page.goto(f"{base_url}/") +@pytest.fixture(scope="session", autouse=True) +def built_site(): + """Build the site before tests and clean up after.""" + # Setup: build the site + result = subprocess.run( + ["uv", "run", "render-engine", "build"], + capture_output=True, + text=True, + ) + if result.returncode != 0: + pytest.fail(f"Failed to build site: {result.stderr}") - axe = Axe() - results = axe.run(page, options={"runOnly": ["wcag2a", "wcag2aa"]}) + yield OUTPUT_DIR - assert ( - len(results["violations"]) == 0 - ), f"Accessibility violations found: {results['violations']}" + # Teardown: remove the output directory + if OUTPUT_DIR.exists(): + shutil.rmtree(OUTPUT_DIR) -def test_destination(loaded_route: str, browser_context) -> None: - """Test that the destinations page loads with seeded data""" - page, base_url = browser_context - response = page.goto(f"{base_url}/{loaded_route}") - assert response.status == 200 +ROUTE_FILES = { + "": "index.html", + "blog": "blog/index.html", + "about.html": "about.html", + "bpd-events": "bpd-events/page.html", + "support.html": "support.html", +} -LANG_ROUTES = ( - "/", - "/about.html", - "/bpd-events/", - "/support.html", - "/blog/", -) +def test_destination(loaded_route: str, built_site: pathlib.Path) -> None: + """Test that the expected output files exist for each route.""" + file_name = ROUTE_FILES[loaded_route] + output_file = built_site / file_name + assert output_file.exists(), f"Expected output file not found: {output_file}" -@pytest.mark.parametrize("route", LANG_ROUTES) -def test_headers_in_language(browser_context, route: str) -> None: - """checks the route and the language of each route""" - page, base_url = browser_context - response = page.goto(f"{base_url}{route}") - assert response.status == 200 - doc_lang = page.evaluate("document.documentElement.lang") - assert doc_lang == "en" +LANG_ROUTES = { + "/": "index.html", + "/about.html": "about.html", + "/bpd-events/": "bpd-events/page.html", + "/support.html": "support.html", + "/blog/": "blog/index.html", +} - axe = Axe() - results = axe.run(page, options={"runOnly": ["wcag2a", "wcag2aa"]}) +@pytest.mark.parametrize("route, file_path", list(LANG_ROUTES.items())) +def test_headers_in_language( + built_site: pathlib.Path, route: str, file_path: str +) -> None: + """Check that each page has lang='en' on the html element.""" + parsed = parse_html(built_site / file_path) assert ( - len(results["violations"]) == 0 - ), f"Accessibility violations found: {results['violations']}" + parsed.lang == "en" + ), f"Expected lang='en' on {route}, got lang='{parsed.lang}'" @pytest.mark.parametrize( - "title, url", + "title, file_path", ( - ("Home", "/"), - ("Blog", "/blog/"), - ("About Us", "/about.html"), - ("BPD Events", "/bpd-events/"), - ("Support Us", "/support.html"), + ("Black Python Devs", "index.html"), + ("Blog | Black Python Devs", "blog/index.html"), + ("About | Black Python Devs", "about.html"), + ("Support | Black Python Devs", "support.html"), ), ) -def test_bpdevs_title_en(browser_context, title: str, url: str) -> None: - page, base_url = browser_context - page.goto(f"{base_url}{url}") - expect(page).to_have_title(f"Black Python Devs | {title}") - - axe = Axe() - results = axe.run(page, options={"runOnly": ["wcag2a", "wcag2aa"]}) - - assert ( - len(results["violations"]) == 0 - ), f"Accessibility violations found: {results['violations']}" - - -def test_mailto_bpdevs(browser_context) -> None: - page, base_url = browser_context - page.goto(base_url) - mailto = page.get_by_role("link", name="email") - expect(mailto).to_have_attribute("href", "mailto:contact@blackpythondevs.com") - - axe = Axe() - results = axe.run(page, options={"runOnly": ["wcag2a", "wcag2aa"]}) - - assert ( - len(results["violations"]) == 0 - ), f"Accessibility violations found: {results['violations']}" - - -@pytest.mark.parametrize( - "url", - ("/blog/",), -) -def test_page_description_in_index_and_blog(browser_context, url: str): - """Checks for the descriptions data in the blog posts. There should be some objects with the class `post-description`""" - page, base_url = browser_context - page.goto(f"{base_url}{url}") - expect(page.locator("p.post-description").first).to_be_visible() - expect(page.locator("p.post-description").first).not_to_be_empty() - - axe = Axe() - results = axe.run(page, options={"runOnly": ["wcag2a", "wcag2aa"]}) - - assert ( - len(results["violations"]) == 0 - ), f"Accessibility violations found: {results['violations']}" - - -def stem_description( - path: pathlib.Path, -) -> Generator[tuple[str, frontmatter.Post], None, None]: - """iterate through a list returning the stem of the file and the contents""" - for entry in path.glob("*.md"): - yield (entry.stem, frontmatter.loads(entry.read_text())) - - -blog_posts = stem_description(pathlib.Path("_posts")) - - -@pytest.mark.parametrize("post", list(blog_posts)) -def test_page_blog_posts(browser_context, post: tuple[str, frontmatter.Post]): - """Checks that the meta page description matches the description of the post""" - page, base_url = browser_context - entry_stem, frontmatter_data = post - - # Convert blog post filename to URL path - # Blog posts are in /blog/ directory in the output - url = f"{base_url}/blog/{entry_stem}.html" - - page.goto(url, timeout=60000, wait_until="networkidle") - - # More robust waiting for the meta description - page.wait_for_selector( - 'meta[name="description"]', - timeout=10000, - state="attached", - ) - - axe = Axe() - results = axe.run(page, options={"runOnly": ["wcag2a", "wcag2aa"]}) - +def test_bpdevs_title_en(built_site: pathlib.Path, title: str, file_path: str) -> None: + """Check that each page has the expected title.""" + parsed = parse_html(built_site / file_path) + assert parsed.title == title, f"Expected title '{title}', got '{parsed.title}'" + + +def test_mailto_bpdevs(built_site: pathlib.Path) -> None: + """Check that the homepage has a mailto link to contact@blackpythondevs.com.""" + parsed = parse_html(built_site / "index.html") + mailto_links = [ + link for link in parsed.links if link.get("href", "").startswith("mailto:") + ] + assert any( + link["href"] == "mailto:contact@blackpythondevs.com" for link in mailto_links + ), "Expected mailto:contact@blackpythondevs.com link not found" + + +def _blog_post_files() -> list[pathlib.Path]: + """Get all blog post HTML files (excluding index and pagination pages).""" + blog_dir = OUTPUT_DIR / "blog" + if not blog_dir.exists(): + return [] + skip = {"index.html"} + return [ + f + for f in sorted(blog_dir.glob("*.html")) + if f.name not in skip and not f.stem.startswith("blog") + ] + + +@pytest.mark.parametrize("post_file", _blog_post_files(), ids=lambda p: p.stem) +def test_blog_post_has_meta_description( + built_site: pathlib.Path, post_file: pathlib.Path +) -> None: + """Check that each blog post has a meta description tag.""" + parsed = parse_html(post_file) assert ( - len(results["violations"]) == 0 - ), f"Accessibility violations found: {results['violations']}" + parsed.meta_description is not None + ), f"Missing meta description in {post_file.name}" diff --git a/tests/test_design.py b/tests/test_design.py deleted file mode 100644 index 68ba9f4..0000000 --- a/tests/test_design.py +++ /dev/null @@ -1,97 +0,0 @@ -import pathlib -import pytest -from playwright.sync_api import Page, sync_playwright -import subprocess -import http.server -import socketserver -import threading -import time -import socket - - -def find_free_port(): - """Find a free port to use for the test server""" - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - s.bind(("", 0)) - s.listen(1) - port = s.getsockname()[1] - return port - - -@pytest.fixture(scope="session") -def built_site(): - """Build the site once for all tests""" - print("Building site for design tests...") - result = subprocess.run( - ["uv", "run", "render-engine", "build"], capture_output=True, text=True - ) - if result.returncode != 0: - pytest.fail(f"Failed to build site: {result.stderr}") - return pathlib.Path("output") - - -@pytest.fixture(scope="session") -def design_test_server(built_site): - """Start a simple HTTP server to serve the built site""" - port = find_free_port() - - class Handler(http.server.SimpleHTTPRequestHandler): - def __init__(self, *args, **kwargs): - super().__init__(*args, directory=str(built_site), **kwargs) - - httpd = socketserver.TCPServer(("", port), Handler) - - # Start server in a thread - server_thread = threading.Thread(target=httpd.serve_forever) - server_thread.daemon = True - server_thread.start() - - # Wait for server to start - time.sleep(1) - - base_url = f"http://localhost:{port}" - - yield base_url - - httpd.shutdown() - - -@pytest.fixture(scope="module") -def per_device_page_url(design_test_server, loaded_profile): - """Returns the url of the live server with device emulation""" - with sync_playwright() as p: - device = p.devices[loaded_profile] - browser = p.chromium.launch() - context = browser.new_context(**device) - page = context.new_page() - try: - yield page, design_test_server - finally: - context.close() - browser.close() - - -@pytest.fixture(scope="session") -def create_test_image(): - image_path = pathlib.Path("./").joinpath("test_images") - - if not image_path.is_dir(): - image_path.mkdir() - return image_path - - -@pytest.mark.design -def test_route_designs( - loaded_route: str, - per_device_page_url: tuple[Page, str], - create_test_image, - request, -) -> None: - """Test that the destinations page loads with seeded data""" - page, base_url = per_device_page_url - response = page.goto(f"{base_url}/{loaded_route}") - page.screenshot( - path=create_test_image.joinpath(request.node.name).with_suffix(".jpg"), - full_page=True, - ) - assert response.status == 200 diff --git a/uv.lock b/uv.lock index 38a36af..72a721e 100644 --- a/uv.lock +++ b/uv.lock @@ -36,27 +36,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl", hash = "sha256:749f0769958ebdc79c173ff0b0670d59051a535fa26e8eba02953dc19eb43205", size = 68797, upload-time = "2025-10-18T17:46:45.663Z" }, ] -[[package]] -name = "axe-core-python" -version = "0.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/be/f4/c85e943a3560e7f91cfe2c8cd85c47cbb504ba8b4303fc4affc0ad71a29a/axe-core-python-0.1.0.tar.gz", hash = "sha256:8a9af93a22f1b47da65be2b9878f83be39155ccc4c7286a8de8e4fc88a7a27c6", size = 114295, upload-time = "2022-09-01T09:48:08.136Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/25/18/39196ff6c961685d2805f4d0fa7030e59983e413427c1cc8123247cf2bc6/axe_core_python-0.1.0-py3-none-any.whl", hash = "sha256:f99f6bd674726c631c5e20b876983b44dff2b8f452a0ccf08f3c23ee45d0aac3", size = 115757, upload-time = "2022-09-01T09:48:09.789Z" }, -] - -[[package]] -name = "axe-playwright-python" -version = "0.1.7" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "playwright" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/74/90/684e8ae7e7563318d197f1bfbacd561a19716e6cdb6420e7e97ef49d427c/axe_playwright_python-0.1.7.tar.gz", hash = "sha256:f0f3d59061abbaee9990d0309f855210ec8fd6ca5bf076f39210289ac0961258", size = 189815, upload-time = "2025-12-01T05:19:31.061Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/02/b0/6460c11d515cb09d90993e948c730a3d4b6673cc5cd99663179bbc48a758/axe_playwright_python-0.1.7-py3-none-any.whl", hash = "sha256:8699e904466cc2206fa2fa414b4d59aab68e34ba8449c6d082896ca1950440e2", size = 156509, upload-time = "2025-12-01T05:19:29.285Z" }, -] - [[package]] name = "binaryornot" version = "0.6.0" @@ -77,12 +56,8 @@ dependencies = [ [package.optional-dependencies] dev = [ - { name = "axe-core-python" }, - { name = "axe-playwright-python" }, - { name = "ephemeral-port-reserve" }, { name = "pre-commit" }, - { name = "pytest-playwright" }, - { name = "pytest-xprocess" }, + { name = "pytest" }, { name = "python-frontmatter" }, { name = "rich" }, { name = "typer" }, @@ -90,12 +65,8 @@ dev = [ [package.metadata] requires-dist = [ - { name = "axe-core-python", marker = "extra == 'dev'", specifier = "==0.1.0" }, - { name = "axe-playwright-python", marker = "extra == 'dev'", specifier = "==0.1.7" }, - { name = "ephemeral-port-reserve", marker = "extra == 'dev'" }, { name = "pre-commit", marker = "extra == 'dev'" }, - { name = "pytest-playwright", marker = "extra == 'dev'" }, - { name = "pytest-xprocess", marker = "extra == 'dev'" }, + { name = "pytest", marker = "extra == 'dev'" }, { name = "python-frontmatter", marker = "extra == 'dev'" }, { name = "pyyaml" }, { name = "render-engine", extras = ["cli"], specifier = ">=2026.3.4a2" }, @@ -228,15 +199,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" }, ] -[[package]] -name = "ephemeral-port-reserve" -version = "1.1.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ef/93/3f0b75f75f94227f67ccfe86f989415e40ac054dd67b55dfac7abdc0a2d2/ephemeral_port_reserve-1.1.4.tar.gz", hash = "sha256:b8f7da2c97090cb0801949dec1d6d40c97220505b742a70935ffbd43234c14b2", size = 3541, upload-time = "2021-02-11T17:18:58.971Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2d/2f/28948390896745afd561dad6a39daf49f74637b7b3398582a649b5acc4c5/ephemeral_port_reserve-1.1.4-py2.py3-none-any.whl", hash = "sha256:dae8da99422c643bb52478ed55d5a8428099092391656ba3726ff30c801600c8", size = 4448, upload-time = "2021-02-11T17:18:57.782Z" }, -] - [[package]] name = "filelock" version = "3.25.2" @@ -246,40 +208,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a4/a5/842ae8f0c08b61d6484b52f99a03510a3a72d23141942d216ebe81fefbce/filelock-3.25.2-py3-none-any.whl", hash = "sha256:ca8afb0da15f229774c9ad1b455ed96e85a81373065fb10446672f64444ddf70", size = 26759, upload-time = "2026-03-11T20:45:37.437Z" }, ] -[[package]] -name = "greenlet" -version = "3.3.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a3/51/1664f6b78fc6ebbd98019a1fd730e83fa78f2db7058f72b1463d3612b8db/greenlet-3.3.2.tar.gz", hash = "sha256:2eaf067fc6d886931c7962e8c6bede15d2f01965560f3359b27c80bde2d151f2", size = 188267, upload-time = "2026-02-20T20:54:15.531Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ac/48/f8b875fa7dea7dd9b33245e37f065af59df6a25af2f9561efa8d822fde51/greenlet-3.3.2-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:aa6ac98bdfd716a749b84d4034486863fd81c3abde9aa3cf8eff9127981a4ae4", size = 279120, upload-time = "2026-02-20T20:19:01.9Z" }, - { url = "https://files.pythonhosted.org/packages/49/8d/9771d03e7a8b1ee456511961e1b97a6d77ae1dea4a34a5b98eee706689d3/greenlet-3.3.2-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab0c7e7901a00bc0a7284907273dc165b32e0d109a6713babd04471327ff7986", size = 603238, upload-time = "2026-02-20T20:47:32.873Z" }, - { url = "https://files.pythonhosted.org/packages/59/0e/4223c2bbb63cd5c97f28ffb2a8aee71bdfb30b323c35d409450f51b91e3e/greenlet-3.3.2-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d248d8c23c67d2291ffd47af766e2a3aa9fa1c6703155c099feb11f526c63a92", size = 614219, upload-time = "2026-02-20T20:55:59.817Z" }, - { url = "https://files.pythonhosted.org/packages/94/2b/4d012a69759ac9d77210b8bfb128bc621125f5b20fc398bce3940d036b1c/greenlet-3.3.2-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ccd21bb86944ca9be6d967cf7691e658e43417782bce90b5d2faeda0ff78a7dd", size = 628268, upload-time = "2026-02-20T21:02:48.024Z" }, - { url = "https://files.pythonhosted.org/packages/7a/34/259b28ea7a2a0c904b11cd36c79b8cef8019b26ee5dbe24e73b469dea347/greenlet-3.3.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b6997d360a4e6a4e936c0f9625b1c20416b8a0ea18a8e19cabbefc712e7397ab", size = 616774, upload-time = "2026-02-20T20:21:02.454Z" }, - { url = "https://files.pythonhosted.org/packages/0a/03/996c2d1689d486a6e199cb0f1cf9e4aa940c500e01bdf201299d7d61fa69/greenlet-3.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:64970c33a50551c7c50491671265d8954046cb6e8e2999aacdd60e439b70418a", size = 1571277, upload-time = "2026-02-20T20:49:34.795Z" }, - { url = "https://files.pythonhosted.org/packages/d9/c4/2570fc07f34a39f2caf0bf9f24b0a1a0a47bc2e8e465b2c2424821389dfc/greenlet-3.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1a9172f5bf6bd88e6ba5a84e0a68afeac9dc7b6b412b245dd64f52d83c81e55b", size = 1640455, upload-time = "2026-02-20T20:21:10.261Z" }, - { url = "https://files.pythonhosted.org/packages/91/39/5ef5aa23bc545aa0d31e1b9b55822b32c8da93ba657295840b6b34124009/greenlet-3.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:a7945dd0eab63ded0a48e4dcade82939783c172290a7903ebde9e184333ca124", size = 230961, upload-time = "2026-02-20T20:16:58.461Z" }, - { url = "https://files.pythonhosted.org/packages/62/6b/a89f8456dcb06becff288f563618e9f20deed8dd29beea14f9a168aef64b/greenlet-3.3.2-cp313-cp313-win_arm64.whl", hash = "sha256:394ead29063ee3515b4e775216cb756b2e3b4a7e55ae8fd884f17fa579e6b327", size = 230221, upload-time = "2026-02-20T20:17:37.152Z" }, - { url = "https://files.pythonhosted.org/packages/3f/ae/8bffcbd373b57a5992cd077cbe8858fff39110480a9d50697091faea6f39/greenlet-3.3.2-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:8d1658d7291f9859beed69a776c10822a0a799bc4bfe1bd4272bb60e62507dab", size = 279650, upload-time = "2026-02-20T20:18:00.783Z" }, - { url = "https://files.pythonhosted.org/packages/d1/c0/45f93f348fa49abf32ac8439938726c480bd96b2a3c6f4d949ec0124b69f/greenlet-3.3.2-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:18cb1b7337bca281915b3c5d5ae19f4e76d35e1df80f4ad3c1a7be91fadf1082", size = 650295, upload-time = "2026-02-20T20:47:34.036Z" }, - { url = "https://files.pythonhosted.org/packages/b3/de/dd7589b3f2b8372069ab3e4763ea5329940fc7ad9dcd3e272a37516d7c9b/greenlet-3.3.2-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c2e47408e8ce1c6f1ceea0dffcdf6ebb85cc09e55c7af407c99f1112016e45e9", size = 662163, upload-time = "2026-02-20T20:56:01.295Z" }, - { url = "https://files.pythonhosted.org/packages/cd/ac/85804f74f1ccea31ba518dcc8ee6f14c79f73fe36fa1beba38930806df09/greenlet-3.3.2-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e3cb43ce200f59483eb82949bf1835a99cf43d7571e900d7c8d5c62cdf25d2f9", size = 675371, upload-time = "2026-02-20T21:02:49.664Z" }, - { url = "https://files.pythonhosted.org/packages/d2/d8/09bfa816572a4d83bccd6750df1926f79158b1c36c5f73786e26dbe4ee38/greenlet-3.3.2-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63d10328839d1973e5ba35e98cccbca71b232b14051fd957b6f8b6e8e80d0506", size = 664160, upload-time = "2026-02-20T20:21:04.015Z" }, - { url = "https://files.pythonhosted.org/packages/48/cf/56832f0c8255d27f6c35d41b5ec91168d74ec721d85f01a12131eec6b93c/greenlet-3.3.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8e4ab3cfb02993c8cc248ea73d7dae6cec0253e9afa311c9b37e603ca9fad2ce", size = 1619181, upload-time = "2026-02-20T20:49:36.052Z" }, - { url = "https://files.pythonhosted.org/packages/0a/23/b90b60a4aabb4cec0796e55f25ffbfb579a907c3898cd2905c8918acaa16/greenlet-3.3.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:94ad81f0fd3c0c0681a018a976e5c2bd2ca2d9d94895f23e7bb1af4e8af4e2d5", size = 1687713, upload-time = "2026-02-20T20:21:11.684Z" }, - { url = "https://files.pythonhosted.org/packages/f3/ca/2101ca3d9223a1dc125140dbc063644dca76df6ff356531eb27bc267b446/greenlet-3.3.2-cp314-cp314-win_amd64.whl", hash = "sha256:8c4dd0f3997cf2512f7601563cc90dfb8957c0cff1e3a1b23991d4ea1776c492", size = 232034, upload-time = "2026-02-20T20:20:08.186Z" }, - { url = "https://files.pythonhosted.org/packages/f6/4a/ecf894e962a59dea60f04877eea0fd5724618da89f1867b28ee8b91e811f/greenlet-3.3.2-cp314-cp314-win_arm64.whl", hash = "sha256:cd6f9e2bbd46321ba3bbb4c8a15794d32960e3b0ae2cc4d49a1a53d314805d71", size = 231437, upload-time = "2026-02-20T20:18:59.722Z" }, - { url = "https://files.pythonhosted.org/packages/98/6d/8f2ef704e614bcf58ed43cfb8d87afa1c285e98194ab2cfad351bf04f81e/greenlet-3.3.2-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:e26e72bec7ab387ac80caa7496e0f908ff954f31065b0ffc1f8ecb1338b11b54", size = 286617, upload-time = "2026-02-20T20:19:29.856Z" }, - { url = "https://files.pythonhosted.org/packages/5e/0d/93894161d307c6ea237a43988f27eba0947b360b99ac5239ad3fe09f0b47/greenlet-3.3.2-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b466dff7a4ffda6ca975979bab80bdadde979e29fc947ac3be4451428d8b0e4", size = 655189, upload-time = "2026-02-20T20:47:35.742Z" }, - { url = "https://files.pythonhosted.org/packages/f5/2c/d2d506ebd8abcb57386ec4f7ba20f4030cbe56eae541bc6fd6ef399c0b41/greenlet-3.3.2-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b8bddc5b73c9720bea487b3bffdb1840fe4e3656fba3bd40aa1489e9f37877ff", size = 658225, upload-time = "2026-02-20T20:56:02.527Z" }, - { url = "https://files.pythonhosted.org/packages/d1/67/8197b7e7e602150938049d8e7f30de1660cfb87e4c8ee349b42b67bdb2e1/greenlet-3.3.2-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:59b3e2c40f6706b05a9cd299c836c6aa2378cabe25d021acd80f13abf81181cf", size = 666581, upload-time = "2026-02-20T21:02:51.526Z" }, - { url = "https://files.pythonhosted.org/packages/8e/30/3a09155fbf728673a1dea713572d2d31159f824a37c22da82127056c44e4/greenlet-3.3.2-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b26b0f4428b871a751968285a1ac9648944cea09807177ac639b030bddebcea4", size = 657907, upload-time = "2026-02-20T20:21:05.259Z" }, - { url = "https://files.pythonhosted.org/packages/f3/fd/d05a4b7acd0154ed758797f0a43b4c0962a843bedfe980115e842c5b2d08/greenlet-3.3.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1fb39a11ee2e4d94be9a76671482be9398560955c9e568550de0224e41104727", size = 1618857, upload-time = "2026-02-20T20:49:37.309Z" }, - { url = "https://files.pythonhosted.org/packages/6f/e1/50ee92a5db521de8f35075b5eff060dd43d39ebd46c2181a2042f7070385/greenlet-3.3.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:20154044d9085151bc309e7689d6f7ba10027f8f5a8c0676ad398b951913d89e", size = 1680010, upload-time = "2026-02-20T20:21:13.427Z" }, - { url = "https://files.pythonhosted.org/packages/29/4b/45d90626aef8e65336bed690106d1382f7a43665e2249017e9527df8823b/greenlet-3.3.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c04c5e06ec3e022cbfe2cd4a846e1d4e50087444f875ff6d2c2ad8445495cf1a", size = 237086, upload-time = "2026-02-20T20:20:45.786Z" }, -] - [[package]] name = "identify" version = "2.6.18" @@ -437,25 +365,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl", hash = "sha256:68a9a4619a666ea6439f2ff250c12a853cd1cbd5158d258bd824a7df6be2f868", size = 21216, upload-time = "2026-03-05T18:34:12.172Z" }, ] -[[package]] -name = "playwright" -version = "1.58.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "greenlet" }, - { name = "pyee" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/f8/c9/9c6061d5703267f1baae6a4647bfd1862e386fbfdb97d889f6f6ae9e3f64/playwright-1.58.0-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:96e3204aac292ee639edbfdef6298b4be2ea0a55a16b7068df91adac077cc606", size = 42251098, upload-time = "2026-01-30T15:09:24.028Z" }, - { url = "https://files.pythonhosted.org/packages/e0/40/59d34a756e02f8c670f0fee987d46f7ee53d05447d43cd114ca015cb168c/playwright-1.58.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:70c763694739d28df71ed578b9c8202bb83e8fe8fb9268c04dd13afe36301f71", size = 41039625, upload-time = "2026-01-30T15:09:27.558Z" }, - { url = "https://files.pythonhosted.org/packages/e1/ee/3ce6209c9c74a650aac9028c621f357a34ea5cd4d950700f8e2c4b7fe2c4/playwright-1.58.0-py3-none-macosx_11_0_universal2.whl", hash = "sha256:185e0132578733d02802dfddfbbc35f42be23a45ff49ccae5081f25952238117", size = 42251098, upload-time = "2026-01-30T15:09:30.461Z" }, - { url = "https://files.pythonhosted.org/packages/f1/af/009958cbf23fac551a940d34e3206e6c7eed2b8c940d0c3afd1feb0b0589/playwright-1.58.0-py3-none-manylinux1_x86_64.whl", hash = "sha256:c95568ba1eda83812598c1dc9be60b4406dffd60b149bc1536180ad108723d6b", size = 46235268, upload-time = "2026-01-30T15:09:33.787Z" }, - { url = "https://files.pythonhosted.org/packages/d9/a6/0e66ad04b6d3440dae73efb39540c5685c5fc95b17c8b29340b62abbd952/playwright-1.58.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f9999948f1ab541d98812de25e3a8c410776aa516d948807140aff797b4bffa", size = 45964214, upload-time = "2026-01-30T15:09:36.751Z" }, - { url = "https://files.pythonhosted.org/packages/0e/4b/236e60ab9f6d62ed0fd32150d61f1f494cefbf02304c0061e78ed80c1c32/playwright-1.58.0-py3-none-win32.whl", hash = "sha256:1e03be090e75a0fabbdaeab65ce17c308c425d879fa48bb1d7986f96bfad0b99", size = 36815998, upload-time = "2026-01-30T15:09:39.627Z" }, - { url = "https://files.pythonhosted.org/packages/41/f8/5ec599c5e59d2f2f336a05b4f318e733077cd5044f24adb6f86900c3e6a7/playwright-1.58.0-py3-none-win_amd64.whl", hash = "sha256:a2bf639d0ce33b3ba38de777e08697b0d8f3dc07ab6802e4ac53fb65e3907af8", size = 36816005, upload-time = "2026-01-30T15:09:42.449Z" }, - { url = "https://files.pythonhosted.org/packages/c8/c4/cc0229fea55c87d6c9c67fe44a21e2cd28d1d558a5478ed4d617e9fb0c93/playwright-1.58.0-py3-none-win_arm64.whl", hash = "sha256:32ffe5c303901a13a0ecab91d1c3f74baf73b84f4bedbb6b935f5bc11cc98e1b", size = 33085919, upload-time = "2026-01-30T15:09:45.71Z" }, -] - [[package]] name = "pluggy" version = "1.6.0" @@ -481,46 +390,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl", hash = "sha256:3b3afd891e97337708c1674210f8eba659b52a38ea5f822ff142d10786221f77", size = 226437, upload-time = "2025-12-16T21:14:32.409Z" }, ] -[[package]] -name = "psutil" -version = "7.2.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740, upload-time = "2026-01-28T18:14:54.428Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/51/08/510cbdb69c25a96f4ae523f733cdc963ae654904e8db864c07585ef99875/psutil-7.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b", size = 130595, upload-time = "2026-01-28T18:14:57.293Z" }, - { url = "https://files.pythonhosted.org/packages/d6/f5/97baea3fe7a5a9af7436301f85490905379b1c6f2dd51fe3ecf24b4c5fbf/psutil-7.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea", size = 131082, upload-time = "2026-01-28T18:14:59.732Z" }, - { url = "https://files.pythonhosted.org/packages/37/d6/246513fbf9fa174af531f28412297dd05241d97a75911ac8febefa1a53c6/psutil-7.2.2-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63", size = 181476, upload-time = "2026-01-28T18:15:01.884Z" }, - { url = "https://files.pythonhosted.org/packages/b8/b5/9182c9af3836cca61696dabe4fd1304e17bc56cb62f17439e1154f225dd3/psutil-7.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312", size = 184062, upload-time = "2026-01-28T18:15:04.436Z" }, - { url = "https://files.pythonhosted.org/packages/16/ba/0756dca669f5a9300d0cbcbfae9a4c30e446dfc7440ffe43ded5724bfd93/psutil-7.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b", size = 139893, upload-time = "2026-01-28T18:15:06.378Z" }, - { url = "https://files.pythonhosted.org/packages/1c/61/8fa0e26f33623b49949346de05ec1ddaad02ed8ba64af45f40a147dbfa97/psutil-7.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9", size = 135589, upload-time = "2026-01-28T18:15:08.03Z" }, - { url = "https://files.pythonhosted.org/packages/81/69/ef179ab5ca24f32acc1dac0c247fd6a13b501fd5534dbae0e05a1c48b66d/psutil-7.2.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00", size = 130664, upload-time = "2026-01-28T18:15:09.469Z" }, - { url = "https://files.pythonhosted.org/packages/7b/64/665248b557a236d3fa9efc378d60d95ef56dd0a490c2cd37dafc7660d4a9/psutil-7.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9", size = 131087, upload-time = "2026-01-28T18:15:11.724Z" }, - { url = "https://files.pythonhosted.org/packages/d5/2e/e6782744700d6759ebce3043dcfa661fb61e2fb752b91cdeae9af12c2178/psutil-7.2.2-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a", size = 182383, upload-time = "2026-01-28T18:15:13.445Z" }, - { url = "https://files.pythonhosted.org/packages/57/49/0a41cefd10cb7505cdc04dab3eacf24c0c2cb158a998b8c7b1d27ee2c1f5/psutil-7.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf", size = 185210, upload-time = "2026-01-28T18:15:16.002Z" }, - { url = "https://files.pythonhosted.org/packages/dd/2c/ff9bfb544f283ba5f83ba725a3c5fec6d6b10b8f27ac1dc641c473dc390d/psutil-7.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1", size = 141228, upload-time = "2026-01-28T18:15:18.385Z" }, - { url = "https://files.pythonhosted.org/packages/f2/fc/f8d9c31db14fcec13748d373e668bc3bed94d9077dbc17fb0eebc073233c/psutil-7.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841", size = 136284, upload-time = "2026-01-28T18:15:19.912Z" }, - { url = "https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090, upload-time = "2026-01-28T18:15:22.168Z" }, - { url = "https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859, upload-time = "2026-01-28T18:15:23.795Z" }, - { url = "https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560, upload-time = "2026-01-28T18:15:25.976Z" }, - { url = "https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997, upload-time = "2026-01-28T18:15:27.794Z" }, - { url = "https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972, upload-time = "2026-01-28T18:15:29.342Z" }, - { url = "https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266, upload-time = "2026-01-28T18:15:31.597Z" }, - { url = "https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737, upload-time = "2026-01-28T18:15:33.849Z" }, - { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" }, -] - -[[package]] -name = "pyee" -version = "13.0.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/8b/04/e7c1fe4dc78a6fdbfd6c337b1c3732ff543b8a397683ab38378447baa331/pyee-13.0.1.tar.gz", hash = "sha256:0b931f7c14535667ed4c7e0d531716368715e860b988770fc7eb8578d1f67fc8", size = 31655, upload-time = "2026-02-14T21:12:28.044Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/c4/b4d4827c93ef43c01f599ef31453ccc1c132b353284fc6c87d535c233129/pyee-13.0.1-py3-none-any.whl", hash = "sha256:af2f8fede4171ef667dfded53f96e2ed0d6e6bd7ee3bb46437f77e3b57689228", size = 15659, upload-time = "2026-02-14T21:12:26.263Z" }, -] - [[package]] name = "pygments" version = "2.19.2" @@ -546,47 +415,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, ] -[[package]] -name = "pytest-base-url" -version = "2.1.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pytest" }, - { name = "requests" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ae/1a/b64ac368de6b993135cb70ca4e5d958a5c268094a3a2a4cac6f0021b6c4f/pytest_base_url-2.1.0.tar.gz", hash = "sha256:02748589a54f9e63fcbe62301d6b0496da0d10231b753e950c63e03aee745d45", size = 6702, upload-time = "2024-01-31T22:43:00.81Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/98/1c/b00940ab9eb8ede7897443b771987f2f4a76f06be02f1b3f01eb7567e24a/pytest_base_url-2.1.0-py3-none-any.whl", hash = "sha256:3ad15611778764d451927b2a53240c1a7a591b521ea44cebfe45849d2d2812e6", size = 5302, upload-time = "2024-01-31T22:42:58.897Z" }, -] - -[[package]] -name = "pytest-playwright" -version = "0.7.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "playwright" }, - { name = "pytest" }, - { name = "pytest-base-url" }, - { name = "python-slugify" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e8/6b/913e36aa421b35689ec95ed953ff7e8df3f2ee1c7b8ab2a3f1fd39d95faf/pytest_playwright-0.7.2.tar.gz", hash = "sha256:247b61123b28c7e8febb993a187a07e54f14a9aa04edc166f7a976d88f04c770", size = 16928, upload-time = "2025-11-24T03:43:22.53Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/76/61/4d333d8354ea2bea2c2f01bad0a4aa3c1262de20e1241f78e73360e9b620/pytest_playwright-0.7.2-py3-none-any.whl", hash = "sha256:8084e015b2b3ecff483c2160f1c8219b38b66c0d4578b23c0f700d1b0240ea38", size = 16881, upload-time = "2025-11-24T03:43:24.423Z" }, -] - -[[package]] -name = "pytest-xprocess" -version = "1.0.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "psutil" }, - { name = "pytest" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/27/6f/e53d349445b4280f6b47bbed87127e994f331c335377d5e72407b376ad46/pytest-xprocess-1.0.2.tar.gz", hash = "sha256:15e270637586eabc56755ee5fcc81c48bdb46ba7ef7c0d5b1b64302d080cc60f", size = 13232, upload-time = "2024-05-19T16:12:21.819Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/63/cf/91b94238c843bfbae0a6674df45015aa3908b91454c496b2eb29e7991255/pytest_xprocess-1.0.2-py3-none-any.whl", hash = "sha256:0b0444d1f789fd9b4ba8b6b38b1d0139f226ab14091db2698a0521c1770523dd", size = 9628, upload-time = "2024-05-19T16:12:19.773Z" }, -] - [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -817,15 +645,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4a/91/48db081e7a63bb37284f9fbcefda7c44c277b18b0e13fbc36ea2335b71e6/typer-0.24.1-py3-none-any.whl", hash = "sha256:112c1f0ce578bfb4cab9ffdabc68f031416ebcc216536611ba21f04e9aa84c9e", size = 56085, upload-time = "2026-02-21T16:54:41.616Z" }, ] -[[package]] -name = "typing-extensions" -version = "4.15.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, -] - [[package]] name = "tzdata" version = "2025.3"