| name | chrome-extension-e2e-playwright |
| description | Playwright E2E testing guide for Chrome extensions. Covers extension loading, MV3 Service Worker startup, page.evaluate() serialization boundary, chrome.storage test access, Shadow DOM interaction, iframe throttling, fixture design, and failure diagnostics. Use this skill only when writing, debugging, or stabilizing Playwright E2E tests for Chrome extensions. Do not use it for manual browser verification; use `agent-browser` or the project-specific `ylc-agent-browser` skill when the task is to open the site and inspect real browser behavior. |
Chrome Extension E2E with Playwright
Playwright ã§ Chrome æ¡åŒµããã¹ãããããã®å®æŠã¬ã€ããéåžžã® Web ã¢ã㪠E2E ãšã¯ç°ãªããã€ã³ãã«çµãã
åã»ã¯ã·ã§ã³ã®è©³çްã³ãŒããšé¢é£ããããã©ãŒã«ã¯ references ãåç
§:
| ããããããš | åç
§å
|
|---|
| æ¡åŒµããŒãã»SW èµ·åã»Extension ID ååŸ | references/getting-started.md |
| page.evaluateã»chrome.storageã»Shadow DOMã»iframe | references/browser-apis.md |
| Fixture èšèšã»URL 管çã»èšºæã»ã¢ã³ããã¿ãŒã³ | references/test-design.md |
1. æ¡åŒµã®ããŒã
Chrome æ¡åŒµã¯ chromium.launchPersistentContext ã§ããããŒãã§ããªãïŒå
¬åŒã¬ã€ãïŒãéåžžã® browser.newContext() ã¯äœ¿ããªããheadless: false ãå®å®ã ããCI ã§ã¯ channel: 'chromium'ïŒNew HeadlessïŒã§ headless å®è¡ãå¯èœãxvfb-run ã§ä»®æ³ãã£ã¹ãã¬ã€ãæäŸããæ¹æ³ããããglobal setup ã§ãã«ãåºåã®ååšç¢ºèªãããŠãããšããã
Playwright 忢± Chromium / Chrome for Testing ã䜿ãããšã Chrome branded build 㯠--load-extension ã Chrome 137 ã§ã--disable-extensions-except ã Chrome 139 ã§åé€ãããignoreDefaultArgs: ['--disable-extensions'] ã¯å¿
é ã§ã¯ãªãããæ¡åŒµãããŒããããªãå Žåã®ãã©ãã«ã·ã¥ãŒããšããŠæçšãMV2 㯠Playwright v1.55 ã§é察å¿ã
2. MV3 Service Worker ã®èµ·åãšåæ¢
MV3 ã® SW ã¯ã€ãã³ãé§åã§ lazy èµ·åãcontent_scripts.matches ã«åã URL ãžã® warmup é·ç§»ã§èµ·åãä¿ããwaitForEvent ã warmup åã«ä»èŸŒã¿ïŒNEW ã€ãã³ãã®ã¿ãã£ããã£ããããïŒãpredicate ã§æ¡åŒµ SW ã ãããã£ã«ã¿ãããå¿
ã timeout ãæå®ããããšïŒCI ãã³ã°å ±åïŒã
warmup ã§èŠã€ãããªãå Žå㯠CDP restart fallbackïŒstopAllWorkers â å warmupïŒã§å詊è¡ããïŒPlaywright #39075ïŒãstopAllWorkers ã¯ãã¹ãåŽ SW ãæ¢ããããæçµææ®µã
SW idle terminationïŒçŽ 30 ç§ïŒåŸã Worker åç
§ã¯çµéšäžæå¹ã ãä¿èšŒã¯ãªããTarget closed ãåºãã Page ãã¹ïŒe2e.html bridgeïŒã«åãæ¿ãããmanifest.json ã® "key" ã§ Extension ID ãåºå®ããæ¹æ³ãããã
3. page.evaluate() ã®ã¹ã³ãŒããš World å¢ç
page.evaluate() ã®ã³ãŒã«ããã¯ã¯ãã©ãŠã¶ããã»ã¹ã§å®è¡ããããNode.js ã¹ã³ãŒãã®å€æ°ã»ã€ã³ããŒãã¯åç
§ã§ãããTypeScript ã¯ãã®ãšã©ãŒãæ€åºããªããåŒæ°ãšããŠæç€ºçã«æž¡ãããcontext.addInitScript() ã§å
±éãã«ããŒã window ã«æ³šå
¥ãããæ¡åŒµ E2E 㯠page.evaluate() ãå€çšãããããaddInitScript ãã¿ãŒã³ã¯ã»ãŒå¿
é ã
åŒæ°ã®å: åºæ¬ã¯ JSON ã·ãªã¢ã©ã€ãºå¯èœãªå€ã®ã¿ããã ã Playwright ã® HandleïŒElementHandle/JSHandleïŒã¯äŸå€çã«æž¡ãããDOM æäœãå¿
èŠãªã evaluateHandle ãéžæè¢ã ããPlaywright çã«ã¯ locator ããŒã¹ã®æäœãåºæ¬ã
World å¢çã®æŽç:
page.evaluate() â Host Page ã® Main World ã®ã¿ïŒContent Script World ã«ã¯å°éããªãïŒ
worker.evaluate() â Extension Service Worker
extPage.evaluate() â Extension Pages (popup.html ç)
- Content Script World / Shadow DOM â
page.evaluate() ã§ shadowRoot ã蟿ãããaddInitScript çµç±
4. chrome.storage ãžã®ã¢ã¯ã»ã¹
ãã¹ããã chrome.storage ãèªã¿æžãããã«ã¯ãæ¡åŒµã®ã³ã³ããã¹ãïŒSW ãŸãã¯æ¡åŒµããŒãžïŒãçµç±ãããWorker ãã¹ïŒworker.evaluate()ãé«éïŒãš Page ãã¹ïŒe2e.html bridge çµç±ïŒã® 2 ã€ãããã
ã©ã³ã¿ã€ã ãã©ãŒã«ããã¯ïŒæšå¥šïŒ: Worker ã詊ã â 倱æããã e2e.html ã«ãã©ãŒã«ããã¯ãe2e.html 㯠React/Zustand ãªãã®æå°ããŒãžãªã®ã§ rehydration ãªã¹ã¯ããªããpopup.html çµç±ã¯ persist ããã«ãŠã§ã¢ã® rehydration ã§ããŒã¿ãäžæžããããããé¿ããã
5. Shadow DOM ã®æäœ
Playwright ã® locator 㯠open Shadow DOM ãããã©ã«ãã§è²«éããïŒå
¬åŒããã¥ã¡ã³ãïŒãXPath ã¯äŸå€ãã€ãŸã page.locator('[data-testid="btn"]') ã§ Shadow DOM å
ã®èŠçŽ ãçŽæ¥æå®ã§ãããpage.evaluate() ã§ shadowRoot ãæåãã©ããŒã¹ããã®ã¯ãcomputed style ã®ååŸãè€åæ¡ä»¶ã®è©äŸ¡ãªã© locator ã§ã¯è¡šçŸãã«ããæäœã«éå®ããã
ãã¹ããµã€ãã®èŠçŽ ã«é®èœãããå Žå㯠3段éãã©ãŒã«ããã¯ã¯ãªãã¯ïŒéåžžã¯ãªã㯠â force ã¯ãªã㯠â JS ã¯ãªãã¯ïŒã§å¯ŸåŠãéåžžã¯ãªãã¯ãæåã«è©Šãã®ãéèŠ â force: true 㯠actionability check ãã¹ããããããã addLocatorHandler() ãçºç«ããªããåžžã«äºéã¯ãªãã¯ãããšãã°ã« UI ãå転ãããããåæ®µéã§ verify ããŠããææ Œãããç¶æ
å€åã®åŸ
æ©ã«ã¯ expect.poll() ãæå¹ã
äºæãã¬ãªãŒããŒã¬ã€ïŒconsent dialog çïŒãžã®å¯Ÿç: page.addLocatorHandler() ã§ YouTube ã®åæãã€ã¢ãã°çãèªååŠçãããCookie äºå泚å
¥ãšè£å®é¢ä¿ã§äœµçšãããåæãã€ã¢ãã°ã®ããã«ãã¯ãªãã¯ã§æ¶ãã UIãã«ã¯ããã©ã«ãåäœïŒæ¶æ»
ãåŸ
æ©ïŒãå®å®ãnoWaitAfter: true ã¯åžžæè¡šç€ºãããèŠçŽ ãããªã¬ãŒã«ããç¹æ®ã±ãŒã¹åãã
6. iframe ã®åãæ±ã
frameLocator() 㯠cross-origin iframe ã§ãåäœããïŒæ¬ãããžã§ã¯ãã® nativeChat.ts ã§ #chatframe ã«äœ¿çšïŒããã ã iframe å
ã® DOM ãçŽæ¥èªã¿åãå¿
èŠãããå ŽåïŒsrc ååŸçïŒã¯ page.evaluate() ã䜿ããChrome 㯠display: none ã® iframe ã匷ãã¹ãããã«ãããããé ãå Žå㯠visibility: hidden + position: fixed + æç€ºç width ã䜿ããSPA é·ç§»åŸã« stale iframe ãæ®ãåé¡ã¯ãsrc ãçŸåšããŒãžãšç
§åããŠæ€åºããã
7. Fixture èšèš
Worker-scoped fixtures ã§ã³ã³ããã¹ããšããŒãžãå
±æãããšããã©ãŠã¶èµ·ååæ°ã倧å¹
ã«åæžã§ããããã ã launchPersistentContext ã«ã¯ããã«ã¹ã¯ãªãŒã³ã«å
¥ã£ãããŒãžã close ãããšå
šããŒãžã§ãã«ã¹ã¯ãªãŒã³ãæ°žä¹
ã«ãããã¯ãããããšãã Chromium ãã°ããããããå
±æããŒãžã¯ close ããªããåç URL æ¢çŽ¢ã¯ãã¹ãæ¬äœãšã¯å¥ã³ã³ããã¹ãã§è¡ããç¶æ
æ±æãé²ãã
8. å€éšãµãŒãã¹äŸåã® URL 管ç
å€éšãµã€ãã® URL ã¯ãã€ç¡å¹åããããäºæž¬ã§ããªããç°å¢å€æ° â ããŒãã³ãŒã â åçæ¢çŽ¢ã®3å±€ãã©ãŒã«ããã¯ãšãåææ¡ä»¶äžæç«æã® test.skip() ã§ graceful degradation ãããå€æåºæº: ç°å¢ã®åé¡ â skipãæ¡åŒµã®æ¯ãèã â assertã
䞊åã¯ãŒã«ãŒå®è¡æã¯ããã¡ã€ã«ããŒã¹ã®ãã£ãã·ã¥ïŒ$TMPDIRïŒã§çºèŠæžã¿ URL ãã¯ãŒã«ãŒéå
±æããéè€æ¢çŽ¢ãæé€ãããglobal-setup ã§æ¯ååé€ããŠã¯ãªãŒã³ã¹ã¿ãŒããä¿èšŒãåè£æ€æ»ã§ã¯ DOM ã³ã³ããã®ååšãã§ãã¯ã§æ©æè±åºããå
šã¹ãããã« deadline ãé©çšããã
9. ãã¹ã倱ææã®èšºæ
testInfo.attach() ã§æ¡åŒµã®å
éšç¶æ
ã JSON ãšããŠèªåä¿åãããv1.56+ ã® page.consoleMessages() / page.pageErrors() ã§äºåŸçã«ãã°ãååŸã§ããv1.57+ ã® worker.on('console') ã§ SW ãã°ãæŸããïŒSW åŽã§äŸå€ãåºãŠããã®ã«ããŒãžã¯éããªäºæ
ãå€ãïŒã
10. ã¢ã³ããã¿ãŒã³
waitForTimeout() ã§å®å®åããªã â expect.poll() ã waitForFunction() ã§æç€ºçã«åŸ
ã€
page.evaluate() å
ã§ã¯ããŒãžã£ãåç
§ããªã â åŒæ°ã§æž¡ãã addInitScript
- ãã«ã¹ã¯ãªãŒã³ã«å
¥ã£ãããŒãžã close ããªã â persistent context ãå£ãã
- iframe ã
display: none ã§é ããªã â ã¹ããããªã³ã°ããã
test.skip() ã§æ¯ãèãçµæãã¹ãããããªã â ãã¹ã䟡å€ã®ç¡å£°å£å
- ãã¹ãã³ãŒããå€§èŠæš¡äžæ¬ãªãã¡ã¯ã¿ããªã â æ®µéçã«è¡ãã倱ææã¯å³ revert