원클릭으로
playwright-tests
Write and debug Playwright E2E tests following project ADR TEST-006 conventions.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Write and debug Playwright E2E tests following project ADR TEST-006 conventions.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Write documentation for DCS Dropzone — a DCS World mod manager consisting of three apps: the Daemon (download, unpack, symlink management), the Webapp (hosted mod discovery), and the Launcher (auto-update). Use this skill whenever the user asks to create, write, review, or structure documentation for this project, including API reference pages, developer guides, landing pages, and — critically — Spec pages for Spec Driven Development (SDD). Spec pages are the primary artefact of SDD: they describe a single system behavior in terms of inputs and resulting system actions, completely void of technical implementation details, and serve as both documentation and the basis for test cases. Trigger on any request to 'write a spec', 'document this behavior', 'write docs', 'create a reference page', 'write a guide', or 'add a landing page' for any part of the project.
Pick an open GitHub issue from flying-dice/dcs-dropzone, implement it on a feature branch, raise a PR, and post progress updates to #dcs-dropzone on Slack using @here. Trigger on any request to 'pick an issue', 'work on an issue', 'grab an issue', 'pick up a ticket', or 'start an issue'.
Review a pull request for flying-dice/dcs-dropzone. Trigger on any request to 'review PR', 'review pull request', 'check PR', or 'look at PR'.
Monitor GitHub Actions and Jenkins pipelines for the current branch/commit and confirm both are green before closing a work item. Trigger on any request to 'check pipelines', 'verify CI', 'watch the build', 'is CI green', 'check before closing', or 'monitor pipelines'.
| name | playwright-tests |
| description | Write and debug Playwright E2E tests following project ADR TEST-006 conventions. |
| allowed-tools | Bash(playwright-cli:*) Bash(bunx:*) Bash(bun:*) mcp__playwright__* |
This skill covers writing, exploring, and debugging Playwright end-to-end tests for this project. It enforces the conventions defined in ADR TEST-006 (/.archgate/adrs/TEST-006-adoption-of-playwright-for-e2e-testing.md).
This project standardises on Chrome for both playwright-cli exploration and the Playwright MCP browser tools (mcp__playwright__browser_*). Both expect Chrome at /opt/google/chrome/chrome on Linux. Do not substitute Chromium or Firefox — they each have subtle rendering and font differences that diverge from CI.
Install Chrome via Playwright (requires sudo for the OS-level dependencies):
sudo bunx playwright install chrome --with-deps
If sudo bunx errors with command not found (because sudo resets PATH and drops ~/.bun/bin), use the absolute path to bunx:
sudo "$(which bunx)" playwright install chrome --with-deps
Verify the binary landed where Playwright expects it:
ls -la /opt/google/chrome/chrome
Playwright is a workspace dev dependency — bun install at the repo root pulls it in. You should not need to install it separately.
playwright-cli open https://www.google.com
A Chrome window (or headless session) should open and report a successful navigation. If you see Chromium distribution 'chrome' is not found at /opt/google/chrome/chrome, repeat step 1.
You have two tools for interactively driving the browser, and both back onto the same Chrome install from setup. Pick whichever fits the moment:
| Tool | When to use |
|---|---|
playwright-cli (terminal binary) | Quick one-off navigation, snapshot, click, eval. Best when you're already in a shell and want to script several steps. |
Playwright MCP (mcp__playwright__browser_* tools) | Interactive exploration where you want to see structured results in the conversation, take screenshots, or chain many steps without leaving tool-call mode. |
Both follow the same exploration discipline (see "Explore First, Code Second" below).
Before writing any test, use the browser to explore the UI as a user would.
Quick playwright-cli example:
playwright-cli open http://localhost:4000
playwright-cli snapshot
playwright-cli click e3
playwright-cli snapshot
The Playwright MCP equivalents (browser_navigate, browser_snapshot, browser_click, etc.) work the same way and return structured results inline.
data-testid attributes — check whether the elements you need to target already have test IDs:
playwright-cli eval "el => el.getAttribute('data-testid')" e5
@packages/testids before writing the test. Do not write a test that relies on CSS or DOM selectors when a test ID can be introduced instead.playwright.config.js@packages/testidstests/webapp/*.spec-pw.ts and tests/daemon/*.spec-pw.ts.spec-pw.tsDo not use CSS selectors, tag names, class names, or DOM position to locate elements. If an element does not have a data-testid, add one to the component source code first. The only exceptions are getByRole and getByText for naturally unique, non-entity-specific elements (e.g., a single "Submit" button).
// FORBIDDEN — never do this
await page.locator('.card-header button.download').click();
await page.locator('#mod-list > div:nth-child(2)').click();
await page.locator('[class*="submit"]').click();
// REQUIRED — use getByTestId with imported constants
await page.getByTestId(DOWNLOAD_BTN_TEST_ID).click();
// ACCEPTABLE — getByRole for naturally unique elements
await page.getByRole('button', { name: 'Submit' }).click();
If you find yourself reaching for a CSS selector, stop and add a data-testid to the component first.
All test IDs are defined in @packages/testids. Import them in both the component and the test. Never pass a raw string to getByTestId().
// BAD
await page.getByTestId('__LOGIN_BUTTON_TEST_ID').click();
// GOOD
import { LOGIN_BUTTON_TEST_ID } from '@packages/testids';
await page.getByTestId(LOGIN_BUTTON_TEST_ID).click();
For elements rendered in a list, define test ID constants as factory functions that embed the React key:
// @packages/testids
export const MOD_CARD_TEST_ID = (key: string) => `__MOD_CARD_TEST_ID-${key}`;
// Test
await page.getByTestId(MOD_CARD_TEST_ID('fa-18c-liveries')).click();
// BAD
const btn = page.getByTestId(LOGIN_BUTTON_TEST_ID);
await btn.click();
// GOOD
await page.getByTestId(LOGIN_BUTTON_TEST_ID).click();
Assert visibility of key elements before performing actions:
await expect(page.getByTestId(HEADER_LOGO_TEST_ID)).toBeVisible();
await expect(page.getByTestId(LOGIN_BUTTON_TEST_ID)).toBeVisible();
await page.getByTestId(LOGIN_BUTTON_TEST_ID).click();
When a test needs a dynamic value (entity ID, slug, etc.) produced by a prior action, read it from a DOM attribute on an element with a data-testid — not from the URL, cookies, or other indirect sources. Components should encode IDs as custom attributes (e.g., mod-id={props.mod.id}) so tests stay coupled to the component contract, not routing.
// GOOD — reads from DOM
const modId = await page.getByTestId(USER_MOD_FORM_TEST_ID).getAttribute("mod-id");
// BAD — parses from URL (breaks if route structure changes)
const modId = page.url().split("/user-mods/")[1];
When you call getAttribute() to extract a value for use later in the test, immediately assert it is non-null. This narrows the TypeScript type from string | null to string and fails early with a clear message.
// GOOD — narrowed, type-safe downstream
const modId = await page.getByTestId(USER_MOD_FORM_TEST_ID).getAttribute("mod-id");
if (!modId) throw new Error("mod-id attribute not found on form");
// BAD — non-null assertion, no runtime safety
const modId = await page.getByTestId(USER_MOD_FORM_TEST_ID).getAttribute("mod-id");
await page.getByTestId(MOD_CARD_TEST_ID(modId!)).toBeVisible();
Each test generates its own data. Never rely on shared or pre-existing state.
test('user can publish a mod', async ({ page }) => {
const modName = `test-mod-${crypto.randomUUID()}`;
// ...
});
// GOOD — auto-retries
await expect(page.getByTestId(MOD_CARD_TEST_ID('abc'))).toContainText('Published');
// BAD — snapshots once, races
const text = await page.getByTestId(MOD_CARD_TEST_ID('abc')).textContent();
expect(text).toContain('Published');
Use waitForURL(), auto-wait, or web-first assertions. Never page.waitForTimeout().
More resilient to minor copy changes and whitespace.
When a component needs a new data-testid:
@packages/testids using __SCREAMING_SNAKE_CASE_TEST_ID format.<button data-testid={MY_BUTTON_TEST_ID}>.data-testid as a prop name (not testId or tid) and drill it to the root element.data-testid to elements that a test actually needs — keep the DOM clean.# Run all tests
PLAYWRIGHT_HTML_OPEN=never bunx playwright test
# Run a specific test file
PLAYWRIGHT_HTML_OPEN=never bunx playwright test tests/webapp/UI_WebappLanding.spec-pw.ts
# Run a specific project
PLAYWRIGHT_HTML_OPEN=never bunx playwright test --project=webapp
# Debug a failing test
PLAYWRIGHT_HTML_OPEN=never bunx playwright test --debug=cli
# Run the failing test in debug mode (background)
PLAYWRIGHT_HTML_OPEN=never bunx playwright test tests/webapp/UI_WebappLanding.spec-pw.ts --debug=cli
# Attach to the paused session
playwright-cli attach tw-abcdef
# Explore the page state
playwright-cli snapshot
playwright-cli eval "el => el.getAttribute('data-testid')" e5
data-testid attributes with constants from @packages/testidsgetByTestId()getByTestId() calls are inlinewaitForTimeout() callsexpect(locator) pattern.spec-pw.ts suffix and lives under tests/webapp/ or tests/daemon/