from __future__ import annotations import os import threading from pathlib import Path import pytest if os.getenv("STACKCHAIN_RUN_RELEASE_E2E") != "1": pytest.skip("packaged Today summary journey runs only in its gated CI job", allow_module_level=True) pytest.importorskip("playwright.sync_api") from playwright.sync_api import expect, sync_playwright from fake_gitea import FakeGiteaServer from test_mobile_offline_issue_release import ACCESS_TOKEN, ROOT, release_server @pytest.mark.parametrize(("width", "height"), [(320, 568), (390, 844)]) def test_release_artifact_reviews_and_shares_a_private_mobile_today_summary( tmp_path: Path, width: int, height: int ): archives = sorted((ROOT / "dist").glob("stackchain-dashboard-*.tar.gz")) assert len(archives) == 1, "browser job must download exactly one assembled release archive" fake = FakeGiteaServer(("127.0.0.1", 0)) fake_thread = threading.Thread(target=fake.serve_forever, daemon=True) fake_thread.start() fake_url = f"http://127.0.0.1:{fake.server_port}" browser_errors: list[str] = [] try: with release_server(archives[0], tmp_path, fake_url) as origin, sync_playwright() as playwright: browser = playwright.chromium.launch(args=["--ignore-certificate-errors"]) context = browser.new_context( viewport={"width": width, "height": height}, ignore_https_errors=True ) page = context.new_page() page.add_init_script( "Object.defineProperty(navigator, 'share', {configurable:true, value:async payload => { window.__sharedSummary = payload; }});" ) page.on("pageerror", lambda error: browser_errors.append(error.stack or str(error))) page.on( "console", lambda message: browser_errors.append(message.text) if message.type == "error" else None, ) page.goto(origin + "/", wait_until="networkidle") page.locator('input[name="device_label"]').fill("Today summary release phone") page.locator('input[name="access_token"]').fill(ACCESS_TOKEN) page.locator("#submit-sign-in").click() page.wait_for_url(origin + "/", wait_until="networkidle") page.evaluate( """ () => { const summary=createTodaySummary({ storage:localStorage,getLogin:()=> 'timmy', share:payload=>navigator.share(payload), copy:text=>navigator.clipboard.writeText(text), createOperationId:()=> 'release-summary-op', resolveTarget:async target=>({...target,kind:'issue',title:'Daily status',state:'open'}), enqueueDurably:async message=>{ window.__queuedSummary=message; return {item:message,durability:'background'}; }, }); const view=createTodaySummaryView({summary,qs:selector=>document.querySelector(selector),escapeHtml:value=>String(value)}); window.__summaryTest={summary,view}; view.open( [{identity:'issue:acme/mobile:41:',title:'Ship mobile capture',context:'acme/mobile#41',actual_minutes:42}], [{identity:'issue:acme/mobile:42:',title:'Review release',context:'acme/mobile#42'}], ); } """ ) sheet = page.locator("#today-summary-sheet") expect(sheet).to_be_visible() expect(page.locator("#today-summary-preview")).to_have_text( "Today\n- Ship mobile capture\n\nTomorrow\n- Review release" ) assert "42m" not in page.locator("#today-summary-preview").inner_text() for control in page.locator(".today-summary-actions button").all(): bounds = control.bounding_box() assert bounds and bounds["height"] >= 44 assert page.evaluate("document.documentElement.scrollWidth <= window.innerWidth") page.locator("#today-summary-destination").fill("acme/mobile#41") page.evaluate( """ () => { window.__summaryTest.summary.setDestination(document.querySelector('#today-summary-destination').value); return window.__summaryTest.view.validateDestination(document.querySelector('#validate-today-summary-destination')); } """ ) expect(page.locator("#today-summary-target")).to_contain_text( "acme/mobile#41 · issue · Daily status" ) expect(page.locator("#post-today-summary")).to_be_enabled() page.evaluate( "window.__summaryTest.view.postDraft(document.querySelector('#post-today-summary'))" ) expect(sheet).to_be_hidden() assert page.evaluate("window.__queuedSummary") == { "kind": "search-reply", "repository": "acme/mobile", "number": 41, "targetKind": "issue", "body": "Today\n- Ship mobile capture\n\nTomorrow\n- Review release", "operationId": "release-summary-op", } page.evaluate( """ window.__summaryTest.view.open( [{identity:'issue:acme/mobile:41:',title:'Ship mobile capture',context:'acme/mobile#41',actual_minutes:42}], [{identity:'issue:acme/mobile:42:',title:'Review release',context:'acme/mobile#42'}] ) """ ) expect(sheet).to_be_visible() page.evaluate( """ () => { window.__summaryTest.summary.includeActuals(true); window.__summaryTest.summary.setNote('Waiting on review'); window.__summaryTest.view.render(); } """ ) expect(page.locator("#today-summary-preview")).to_contain_text( "Ship mobile capture (42m)" ) page.evaluate( "window.__summaryTest.view.shareDraft(document.querySelector('#share-today-summary'))" ) expect(sheet).to_be_hidden() assert page.evaluate("window.__sharedSummary") == { "title": "Today summary", "text": "Today\n- Ship mobile capture (42m)\n\nTomorrow\n- Review release\n\nNote\nWaiting on review", } assert page.evaluate( "localStorage.getItem('stackchain.today-summary-draft.v1.timmy')" ) is None assert browser_errors == [] browser.close() finally: fake.shutdown() fake.server_close() fake_thread.join(timeout=5)