.cursor/skills/write-and-verify-pw-test/SKILL.md
Before starting, ensure Chromium is available:
cd app/client && npx playwright install chromium 2>/dev/null
Run the configure-env.sh script to set up app/client/playwright/.env. It creates the file if missing and merges new values with any existing ones.
node .cursor/skills/write-and-verify-pw-test/scripts/configure-env.js \
--PLAYWRIGHT_BASE_URL=https://target-dp.appsmith.com \
[email protected] \
--PASSWORD=secret
Pass only the variables the caller provided. The script preserves existing values for anything not overridden. Supported variables:
| Variable | Required | Notes |
|---|---|---|
PLAYWRIGHT_BASE_URL | Yes | Target deployment URL |
USERNAME | If no .env exists | Login credentials |
PASSWORD | If no .env exists | Login credentials |
DATASOURCE_HOST | No | For datasource tests |
GITEA_BASE_URL | No | For git tests |
GITEA_API_TOKEN | No | For git tests |
GIT_CLONE_URL | No | For git tests |
PW_FLAG_OVERRIDES | No | JSON string, e.g. '{"flag": true}' |
PW_FLAG_OVERRIDES can go in .env via the script, or be passed inline when running tests (see Step 6).
Before writing any spec, read these files for conventions (they are auto-applied when editing playwright/**/*.ts, but read them explicitly here):
.cursor/rules/playwright.mdc — full conventions (POM rules, selectors, assertions, wait strategy)Key rules to internalize:
import { test, expect } from "../../fixtures" (not @playwright/test)getByRole() > getByLabel() > getByTestId() > SELECTORS from constants. Never raw CSS.waitForTimeout, no networkidle. Wait for meaningful elements.await expect(locator).toBeVisible(), not expect(await locator.isVisible()).toBe(true)playwright/constants/api-routes.ts, selectors from playwright/constants/selectors.ts, routes from playwright/constants/routes.ts. Never hardcode.Page. No assertions in POMs. 1-5 lines per method.request.get/request.post, grep the server for the controller to confirm exact parameter names.| Tier | Directory | When |
|---|---|---|
| smoke | playwright/tests/smoke/ | Login, create app, basic alive checks |
| sanity | playwright/tests/sanity/<feature>/ | Core flows for a feature area |
| regression | playwright/tests/regression/<feature>/ | Edge cases, complex interactions |
| ee | playwright/tests/ee/{sanity,regression}/<feature>/ | EE-only features |
If the caller specifies a tier, use it. Otherwise, default to sanity.
Feature subdirectories are mandatory under sanity/ and regression/.
Read playwright.config.ts to understand the current projects and their setup chains.
Use an existing project when the new spec:
testDirtests/sanity/widgets/chart.spec.ts → use the sanity projectCreate a new project when the new spec:
playwright.mdc (Parallelization section):
playwright/fixtures/<name>.setup.ts — performs the operation, writes state to playwright/.state/<name>.jsonplaywright/fixtures/<name>.teardown.ts — cleans upplaywright/helpers/<name>-state.ts — typed accessor for test filesplaywright.config.ts with dependencies, testDir, and teardownSplit into multiple spec files when:
migration-mysql.spec.ts, migration-postgres.spec.ts, migration-modal-form.spec.ts — all under regression/git/, all share the migration-setup project, each focused on one pageRule of thumb: One test.describe per spec file, one concern per test(). If the prompt covers multiple concerns, prefer multiple small specs over one large one.
Write the spec file following these patterns. Reference existing examples:
Simple spec (smoke-level):
import { test, expect } from "../../fixtures";
import { ROUTES } from "../../constants/routes";
test.describe("Smoke — Feature Name", () => {
test("behavior description in lowercase", async ({ page }) => {
await page.goto(ROUTES.applications);
await expect(page.getByRole("button", { name: /new/i })).toBeVisible();
});
});
Spec with fixture workspace/app (sanity/regression):
import { test, expect } from "../../fixtures";
import { SELECTORS } from "../../constants/selectors";
test.describe("Feature — Specific Area", () => {
test("filters table by country", async ({ page, app }) => {
await page.goto(app.url);
await expect(page.locator(SELECTORS.widgetInDeployed("textwidget")).first()).toBeVisible();
// test logic here
});
});
If a POM is needed, create it under playwright/page-objects/ (or components/ for reusable widgets). Follow existing patterns like home.page.ts.
After writing each file, run:
cd app/client && npx eslint <spec-file-path> --ext .ts
Fix any issues before proceeding.
The config chains setup projects: signup-setup → setup → your test project. This handles fresh deployments automatically:
playwright/auth/user.jsonThis chain runs automatically when you use --project. Never bypass it by running test files directly without a project flag.
Critical: Each project in playwright.config.ts has its own testDir, dependencies, and sometimes testIgnore. The spec must be run under the project whose testDir matches and whose dependency chain includes the required setup.
Read playwright.config.ts and match your spec's directory to a project. Current mapping:
| Spec directory | --project flag | Setup chain |
|---|---|---|
tests/smoke/ | smoke | signup → auth |
tests/sanity/ | sanity | signup → auth |
tests/regression/ (non-git) | regression | signup → auth |
tests/regression/git/ | regression-git | signup → auth → migration-setup (+ teardown) |
Why this matters: Some projects have extra setup phases (e.g., regression-git depends on migration-setup which imports a Git app and creates datasources). Specs in those directories read shared state produced by the setup (e.g., loadMigrationState()). Running them under the wrong project skips that setup and they fail immediately.
When writing a new spec that needs a custom setup project (e.g., it relies on pre-imported data, a connected datasource, or shared app state):
playwright.mdc (Parallelization section)playwright.config.ts with proper dependencies and testDirIf the config has changed since this skill was written, always read playwright.config.ts to get the current project list. Don't rely on this table alone.
cd app/client && npx playwright test <spec-file-path> --project=<project>
If PW_FLAG_OVERRIDES was provided:
cd app/client && PW_FLAG_OVERRIDES='{"flag_name": true}' npx playwright test <spec-file-path> --project=<project>
Timeout: Set block_until_ms to at least 120000 (2 min). Tests can take 60s+ with signup + auth setup on first run against a fresh deployment.
If the test fails, classify the failure:
locator.click: Target closed → wrong selector or premature navigationTimeout + element exists in DOM but not visible → missing wait or wrong locatorstrict mode violation → locator matches multiple elements, needs .first() or refinementError: expect(received).toHaveText(expected) where expected value is clearly wrong → bad test datawaiting for locator → selector doesn't match anything, check the actual pageAction: Read the error output, fix the spec, re-run. This is attempt N+1.
expect(received).toHaveText(expected) where expected is correct but received shows clearly wrong app behaviornet::ERR_CONNECTION_REFUSED → deployment is down401 / 403 after successful auth setup → permission regressionAction: Stop retrying. Switch to the diagnose-pw-failure skill. See .cursor/skills/diagnose-pw-failure/SKILL.md.
Default to "fix the spec" for the first 2 attempts. If the same assertion fails 3 times with the same received value, it's likely a product bug — switch to diagnosis.
After the test passes (or after exhausting retries), provide a summary:
On success:
## Playwright Test Result: PASS
**Spec**: playwright/tests/sanity/widgets/table-filter.spec.ts
**Deployment**: https://my-dp.appsmith.com
**Project**: sanity
**Attempts**: 2 (1 fix applied: wrong selector for filter button)
### What was tested
- Table widget renders with data
- Filtering by country "Ba" shows Bangladesh
On failure (product bug):
## Playwright Test Result: FAIL (product bug suspected)
**Spec**: playwright/tests/sanity/widgets/table-filter.spec.ts
**Deployment**: https://my-dp.appsmith.com
**Attempts**: 3 (spec verified correct)
### Failure
- Table filter returns empty results when filtering by "Ba"
- Expected: rows containing "Bangladesh"
- Received: 0 rows
- Screenshot: playwright/results/<test-name>/screenshot.png
### Diagnosis
[See diagnose-pw-failure output]