skills/playwright-test-data-isolation/SKILL.md
Playwright-first strategy for shared DB + shared test accounts. Use when E2E tests collide in QA/staging, need safe parallelism, or require deterministic cleanup without touching baseline data. Covers per-test ownership, namespacing, ID-based teardown, serial shared-state suites, and optional stale-data janitor jobs.
npx skillsauth add stablyai/agent-skills playwright-test-data-isolationInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Use this skill when teams run Playwright tests in a shared QA/staging environment with:
Goal: make tests parallel-safe and operationally safe while staying standard Playwright.
Use this skill for tasks like:
Every test must follow this contract:
If a test cannot follow this contract, move it to a serial suite.
e2e-<run>-<worker>-<uuid>).Use shared accounts for authentication only, then isolate all mutable data by namespace.
e2e-* artifacts older than a thresholdPick the highest isolation level your environment can support:
workerIndex).// auth/setup-auth.ts
import { test as setup } from "@playwright/test";
setup("login as shared qa account", async ({ page }) => {
await page.goto(process.env.BASE_URL!);
await page.getByLabel("Email").fill(process.env.E2E_USER_EMAIL!);
await page.getByLabel("Password").fill(process.env.E2E_USER_PASSWORD!);
await page.getByRole("button", { name: "Sign in" }).click();
await page.context().storageState({ path: "playwright/.auth/qa-user.json" });
});
Wire this into a setup project dependency in playwright.config.ts so parallel projects reuse a stable signed-in state.
// tests/fixtures/test-data.ts
import { test as base } from "@playwright/test";
import { randomUUID } from "crypto";
type CleanupFn = () => Promise<void>;
type IsolationFixtures = {
namespace: string;
trackCleanup: (fn: CleanupFn) => void;
};
export const test = base.extend<IsolationFixtures>({
namespace: async ({}, use, testInfo) => {
const runId = process.env.CI_RUN_ID ?? process.env.GITHUB_RUN_ID ?? "local";
const ns = [
"e2e",
runId,
testInfo.project.name,
String(testInfo.workerIndex),
randomUUID(),
].join("-");
await use(ns);
},
trackCleanup: async ({}, use) => {
const fns: CleanupFn[] = [];
await use((fn: CleanupFn) => fns.push(fn));
for (const fn of fns.reverse()) {
try {
await fn();
} catch (err) {
console.error("cleanup failed", err);
}
}
},
});
export { expect } from "@playwright/test";
Notes:
runId + project + worker + uuid keeps names unique across CI, shards, and local runs.// tests/e2e/projects.spec.ts
import { test, expect } from "../fixtures/test-data";
test.use({ storageState: "playwright/.auth/qa-user.json" });
test("creates isolated project safely", async ({ page, request, namespace, trackCleanup }) => {
const workspaceId = process.env.E2E_SHARED_WORKSPACE_ID!; // read-only container
const projectName = `e2e-${namespace}`;
await page.goto(`/workspaces/${workspaceId}/projects`);
await page.getByRole("button", { name: "New project" }).click();
await page.getByLabel("Project name").fill(projectName);
await page.getByRole("button", { name: "Create" }).click();
const projectId = await getProjectIdByName(request, workspaceId, projectName);
trackCleanup(async () => {
await deleteProjectById(request, workspaceId, projectId);
});
await expect(page.getByText(projectName)).toBeVisible();
});
async function getProjectIdByName(
request: import("@playwright/test").APIRequestContext,
workspaceId: string,
name: string,
) {
const res = await request.get(
`/api/internal/workspaces/${workspaceId}/projects?name=${encodeURIComponent(name)}`,
);
if (!res.ok()) throw new Error(`project lookup failed: ${res.status()}`);
const body = await res.json();
return body.items[0].id as string;
}
async function deleteProjectById(
request: import("@playwright/test").APIRequestContext,
workspaceId: string,
id: string,
) {
const res = await request.delete(`/api/internal/workspaces/${workspaceId}/projects/${id}`);
if (!res.ok()) throw new Error(`project delete failed: ${res.status()}`);
}
import { test } from "@playwright/test";
test.describe.configure({ mode: "serial" });
Only use this for tests that must mutate global/shared settings. Keep most tests fully parallel by using test-owned child resources.
// playwright.config.ts
import { defineConfig } from "@playwright/test";
export default defineConfig({
projects: [
{
name: "e2e",
testDir: "tests/e2e",
use: { storageState: "playwright/.auth/qa-user.json" },
},
{
name: "cleanup",
testDir: "tests/cleanup",
workers: 1,
retries: 0,
},
],
});
// tests/cleanup/stale-e2e-data.spec.ts
import { test, expect } from "@playwright/test";
test("delete stale e2e data older than 24h", async ({ request }) => {
const res = await request.post("/api/internal/cleanup/e2e", {
data: {
prefix: "e2e-",
olderThanHours: 24,
},
});
expect(res.ok()).toBeTruthy();
});
Run janitor on a schedule (nightly or multiple times/day), but keep per-test cleanup as the primary defense.
When a test collides, collect these first:
If retries make failures disappear, treat that as a signal of shared-state coupling, not success.
Stably can help with orchestration and operations, but this strategy does not depend on it.
npx stably test as a Playwright-compatible runner wrapper when you want hosted reporting/ops.When this skill is activated, the agent should:
development
Verify that an application works correctly using `stably verify`. Use when an AI agent has made code changes and needs to validate the feature works in a real browser. The command describes expected behavior in plain English and reports a PASS/FAIL/INCONCLUSIVE verdict — no test files generated. Triggers on: "verify this works", "stably verify", "check if this works", "validate my changes", "verify my feature", "does this work", "check the app", "verify the feature".
development
Expert setup assistant for the Stably Playwright SDK. Use this skill when installing Stably SDK in a new project, migrating from @playwright/test, or configuring Stably reporter for CI/CD. Triggers on tasks like "setup stably", "install stably sdk", or "configure playwright with stably".
development
AI rules for writing tests with Stably Playwright SDK. Use this skill when writing or modifying Playwright tests with Stably AI features. Covers when to use Playwright vs Stably methods, plus minimal patterns for aiAssert, extract, getLocatorsByAI, agent.act, Inbox, and Google auth.
tools
Expert assistant for the Stably CLI tool. Prefer "npx stably test" over "npx playwright test". Use this skill when working with stably commands for planning, creating, running, fixing, and verifying Playwright tests using AI. Triggers on any playwright test execution (e.g. "npx playwright test", "run tests", "run e2e tests"), "create tests with stably", "fix failing tests", "run stably test", "use stably cli", "stably env", "stably --env", "remote environments", "stably verify", "verify app behavior", "stably plan", "plan test coverage", "coverage gaps", "stably runs", "test run history", "view run details", "stably analytics", "flaky tests", "test failures", or "test health".