playwright-test-data-isolation — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited playwright-test-data-isolation (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
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:
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.