{"id":"ac95b61d-ee57-4555-a19c-954bdacafd41","shortId":"hNzgd7","kind":"skill","title":"playwright-test-data-isolation","tagline":"Playwright-first strategy for shared DB + shared test accounts. Use when E2E\ntests collide in QA/staging, need safe parallelism, or require deterministic\ncleanup without touching baseline data. Covers per-test ownership,\nnamespacing, ID-based teardown, serial shared-state suites,","description":"# Playwright Test Data Isolation\n\nUse this skill when teams run Playwright tests in a shared QA/staging environment with:\n\n- A shared database (often production-connected)\n- Shared test user accounts\n- Parallel CI and local test runs\n- Flaky tests caused by data collisions, cross-test interference, or unsafe cleanup\n\nGoal: make tests parallel-safe and operationally safe while staying standard Playwright.\n\n## When To Use This Skill\n\nUse this skill for tasks like:\n\n- \"Our Playwright tests share a database and keep colliding\"\n- \"How do we run E2E safely with shared accounts?\"\n- \"How should we isolate test data in CI?\"\n- \"How do we clean up test data without deleting real data?\"\n- \"How do we keep parallel Playwright runs stable?\"\n- \"How do we design fixtures for safe create-and-cleanup?\"\n\n## Core Contract (Non-Negotiable)\n\nEvery test must follow this contract:\n\n1. Create the entities it needs (test-owned data).\n2. Mutate only entities it created.\n3. Treat shared baselines (accounts, workspaces, org settings) as read-only.\n4. Clean up by exact IDs captured during the test (never broad filters).\n\nIf a test cannot follow this contract, move it to a serial suite.\n\n## Non-Negotiable Guardrails\n\n- Treat shared users and shared workspaces as read-only containers.\n- Never update shared workspace settings, membership, billing, or global toggles in parallel suites.\n- Prefix test-created records with a unique namespace (`e2e-<run>-<worker>-<uuid>`).\n- Delete by exact IDs; never run broad delete filters in test teardown.\n- Keep a scheduled stale-data janitor as backup, not as the primary cleanup mechanism.\n\n## Recommended Architecture\n\nUse shared accounts for authentication only, then isolate all mutable data by namespace.\n\n- Shared accounts: identity only\n- Test data: fully owned by one test via namespace\n- Cleanup: deterministic, ID-based, reverse order\n- Shared-state mutation suites: serialized\n- Scheduled cleanup: removes stale `e2e-*` artifacts older than a threshold\n\n## Account Strategy\n\nPick the highest isolation level your environment can support:\n\n1. Best: one account per worker (pool accounts and map by `workerIndex`).\n2. Acceptable: shared account for sign-in only, test-owned child data for all writes.\n3. Avoid: many tests mutating top-level account/workspace settings in parallel.\n\n## Implementation Pattern (Playwright-Native)\n\n### 1) Reuse auth state for shared accounts (setup project)\n\n```ts\n// auth/setup-auth.ts\nimport { test as setup } from \"@playwright/test\";\n\nsetup(\"login as shared qa account\", async ({ page }) => {\n  await page.goto(process.env.BASE_URL!);\n  await page.getByLabel(\"Email\").fill(process.env.E2E_USER_EMAIL!);\n  await page.getByLabel(\"Password\").fill(process.env.E2E_USER_PASSWORD!);\n  await page.getByRole(\"button\", { name: \"Sign in\" }).click();\n  await page.context().storageState({ path: \"playwright/.auth/qa-user.json\" });\n});\n```\n\nWire this into a `setup` project dependency in `playwright.config.ts` so parallel projects reuse a stable signed-in state.\n\n### 2) Add namespace + cleanup tracker fixtures\n\n```ts\n// tests/fixtures/test-data.ts\nimport { test as base } from \"@playwright/test\";\nimport { randomUUID } from \"crypto\";\n\ntype CleanupFn = () => Promise<void>;\n\ntype IsolationFixtures = {\n  namespace: string;\n  trackCleanup: (fn: CleanupFn) => void;\n};\n\nexport const test = base.extend<IsolationFixtures>({\n  namespace: async ({}, use, testInfo) => {\n    const runId = process.env.CI_RUN_ID ?? process.env.GITHUB_RUN_ID ?? \"local\";\n    const ns = [\n      \"e2e\",\n      runId,\n      testInfo.project.name,\n      String(testInfo.workerIndex),\n      randomUUID(),\n    ].join(\"-\");\n    await use(ns);\n  },\n\n  trackCleanup: async ({}, use) => {\n    const fns: CleanupFn[] = [];\n    await use((fn: CleanupFn) => fns.push(fn));\n\n    for (const fn of fns.reverse()) {\n      try {\n        await fn();\n      } catch (err) {\n        console.error(\"cleanup failed\", err);\n      }\n    }\n  },\n});\n\nexport { expect } from \"@playwright/test\";\n```\n\nNotes:\n\n- `runId + project + worker + uuid` keeps names unique across CI, shards, and local runs.\n- Reverse-order cleanup (LIFO) avoids dependency-order teardown bugs.\n\n### 3) Create child resources in shared containers only\n\n```ts\n// tests/e2e/projects.spec.ts\nimport { test, expect } from \"../fixtures/test-data\";\n\ntest.use({ storageState: \"playwright/.auth/qa-user.json\" });\n\ntest(\"creates isolated project safely\", async ({ page, request, namespace, trackCleanup }) => {\n  const workspaceId = process.env.E2E_SHARED_WORKSPACE_ID!; // read-only container\n  const projectName = `e2e-${namespace}`;\n\n  await page.goto(`/workspaces/${workspaceId}/projects`);\n  await page.getByRole(\"button\", { name: \"New project\" }).click();\n  await page.getByLabel(\"Project name\").fill(projectName);\n  await page.getByRole(\"button\", { name: \"Create\" }).click();\n\n  const projectId = await getProjectIdByName(request, workspaceId, projectName);\n  trackCleanup(async () => {\n    await deleteProjectById(request, workspaceId, projectId);\n  });\n\n  await expect(page.getByText(projectName)).toBeVisible();\n});\n\nasync function getProjectIdByName(\n  request: import(\"@playwright/test\").APIRequestContext,\n  workspaceId: string,\n  name: string,\n) {\n  const res = await request.get(\n    `/api/internal/workspaces/${workspaceId}/projects?name=${encodeURIComponent(name)}`,\n  );\n  if (!res.ok()) throw new Error(`project lookup failed: ${res.status()}`);\n  const body = await res.json();\n  return body.items[0].id as string;\n}\n\nasync function deleteProjectById(\n  request: import(\"@playwright/test\").APIRequestContext,\n  workspaceId: string,\n  id: string,\n) {\n  const res = await request.delete(`/api/internal/workspaces/${workspaceId}/projects/${id}`);\n  if (!res.ok()) throw new Error(`project delete failed: ${res.status()}`);\n}\n```\n\n### 4) Isolate true shared-state tests in serial suites\n\n```ts\nimport { test } from \"@playwright/test\";\n\ntest.describe.configure({ mode: \"serial\" });\n```\n\nOnly use this for tests that must mutate global/shared settings. Keep most tests fully parallel by using test-owned child resources.\n\n### 5) Add a stale-data janitor project (backup safety net)\n\n```ts\n// playwright.config.ts\nimport { defineConfig } from \"@playwright/test\";\n\nexport default defineConfig({\n  projects: [\n    {\n      name: \"e2e\",\n      testDir: \"tests/e2e\",\n      use: { storageState: \"playwright/.auth/qa-user.json\" },\n    },\n    {\n      name: \"cleanup\",\n      testDir: \"tests/cleanup\",\n      workers: 1,\n      retries: 0,\n    },\n  ],\n});\n```\n\n```ts\n// tests/cleanup/stale-e2e-data.spec.ts\nimport { test, expect } from \"@playwright/test\";\n\ntest(\"delete stale e2e data older than 24h\", async ({ request }) => {\n  const res = await request.post(\"/api/internal/cleanup/e2e\", {\n    data: {\n      prefix: \"e2e-\",\n      olderThanHours: 24,\n    },\n  });\n\n  expect(res.ok()).toBeTruthy();\n});\n```\n\nRun janitor on a schedule (nightly or multiple times/day), but keep per-test cleanup as the primary defense.\n\n## Collision Debugging Checklist\n\nWhen a test collides, collect these first:\n\n- Namespace used by each failing test\n- Worker index and project name\n- IDs created by each test before cleanup\n- Which shared entity was mutated (if any)\n\nIf retries make failures disappear, treat that as a signal of shared-state coupling, not success.\n\n## Optional: If Team Uses Stably\n\nStably can help with orchestration and operations, but this strategy does not depend on it.\n\n- Use `npx stably test` as a Playwright-compatible runner wrapper when you want hosted reporting/ops.\n- Use Stably environments to inject shared account credentials by environment.\n- Use Stably schedules/cloud workers for janitor jobs and large parallel runs.\n- Keep the same isolation contract regardless of runner.\n\n## Anti-Patterns To Block\n\n- Parallel tests writing to the same shared entity\n- Shared mutable fixtures reused across files\n- Cleanup using broad filters like \"delete all e2e rows\"\n- Relying on execution order\n- Using retries to mask shared-data collisions\n- Treating nightly cleanup as the only cleanup\n\n## Agent Output Requirements\n\nWhen this skill is activated, the agent should:\n\n1. Confirm environment constraints (shared DB, shared accounts, parallelism level).\n2. Produce or update a namespace fixture and deterministic cleanup tracker.\n3. Classify tests into parallel-safe vs shared-state-mutation suites.\n4. Refactor at least one target test to create-and-own data.\n5. Add or propose a stale-data cleanup project and schedule.\n6. Return a short risk report listing remaining unsafe tests.\n\n## Rollout Plan\n\n1. Add namespace + cleanup fixtures.\n2. Migrate flaky mutable tests to create-and-own data.\n3. Add cleanup project and schedule.\n4. Move shared-state mutation tests to serial mode.\n5. Increase workers only after collision-free runs.\n\n## Success Criteria\n\n- Parallel runs no longer collide on shared data.\n- Teardown never touches non-test data.\n- Flake rate drops for data-dependent tests.\n- Shared environment remains stable for manual QA and CI.","tags":["playwright","test","data","isolation","agent","skills","stablyai","agent-skills"],"capabilities":["skill","source-stablyai","skill-playwright-test-data-isolation","topic-agent-skills"],"categories":["agent-skills"],"synonyms":[],"warnings":[],"endpointUrl":"https://skills.sh/stablyai/agent-skills/playwright-test-data-isolation","protocol":"skill","transport":"skills-sh","auth":{"type":"none","details":{"cli":"npx skills add stablyai/agent-skills","source_repo":"https://github.com/stablyai/agent-skills","install_from":"skills.sh"}},"qualityScore":"0.453","qualityRationale":"deterministic score 0.45 from registry signals: · indexed on github topic:agent-skills · 6 github stars · SKILL.md body (9,027 chars)","verified":false,"liveness":"unknown","lastLivenessCheck":null,"agentReviews":{"count":0,"score_avg":null,"cost_usd_avg":null,"success_rate":null,"latency_p50_ms":null,"narrative_summary":null,"summary_updated_at":null},"enrichmentModel":"deterministic:skill-github:v1","enrichmentVersion":1,"enrichedAt":"2026-05-18T19:15:07.076Z","embedding":null,"createdAt":"2026-05-18T13:22:55.911Z","updatedAt":"2026-05-18T19:15:07.076Z","lastSeenAt":"2026-05-18T19:15:07.076Z","tsv":"'/api/internal/cleanup/e2e':855 '/api/internal/workspaces':705,745 '/fixtures/test-data':618 '/projects':651,707,747 '/workspaces':649 '0':726,833 '1':188,369,415,831,1058,1129 '2':198,381,491,1068,1134 '24':860 '24h':848 '3':204,398,604,1079,1145 '4':216,758,1092,1151 '5':798,1105,1161 '6':1117 'accept':382 'account':15,77,138,208,311,323,358,372,376,384,421,437,977,1065 'account/workspace':406 'across':587,1017 'activ':1054 'add':492,799,1106,1130,1146 'agent':1047,1056 'anti':1001 'anti-pattern':1000 'apirequestcontext':696,736 'architectur':308 'artifact':353 'async':438,525,550,627,679,690,730,849 'auth':417 'auth/setup-auth.ts':425 'authent':313 'avoid':399,598 'await':440,444,452,460,467,546,555,567,647,652,659,665,673,680,685,703,722,743,853 'backup':300,806 'base':42,339,502 'base.extend':523 'baselin':32,207 'best':370 'bill':263 'block':1004 'bodi':721 'body.items':725 'broad':227,286,1021 'bug':603 'button':462,654,667 'cannot':232 'captur':222 'catch':569 'caus':86 'checklist':885 'child':393,606,796 'ci':79,146,588,1203 'classifi':1080 'clean':150,217 'cleanup':29,96,176,305,335,349,494,572,596,827,878,910,1019,1042,1046,1077,1113,1132,1147 'cleanupfn':510,518,554,558 'click':466,658,670 'collect':890 'collid':20,129,889,1176 'collis':89,883,1039,1167 'collision-fre':1166 'compat':963 'confirm':1059 'connect':73 'console.error':571 'const':521,528,537,552,562,632,643,671,701,720,741,851 'constraint':1061 'contain':256,610,642 'contract':178,187,235,996 'core':177 'coupl':932 'cover':34 'creat':174,189,203,273,605,623,669,905,1101,1141 'create-and-cleanup':173 'create-and-own':1100,1140 'credenti':978 'criteria':1171 'cross':91 'cross-test':90 'crypto':508 'data':4,33,51,88,144,153,157,197,297,319,327,394,803,845,856,1038,1104,1112,1144,1179,1186,1192 'data-depend':1191 'databas':69,126 'db':12,1063 'debug':884 'default':816 'defens':882 'defineconfig':812,817 'delet':155,280,287,755,842,1024 'deleteprojectbyid':681,732 'depend':478,600,952,1193 'dependency-ord':599 'design':169 'determinist':28,336,1076 'disappear':922 'drop':1189 'e2e':18,134,279,352,449,457,539,635,645,820,844,858,1026 'email':446,451 'encodeuricompon':709 'entiti':191,201,913,1012 'environ':65,366,973,980,1060,1196 'err':570,574 'error':715,753 'everi':182 'exact':220,282 'execut':1030 'expect':576,616,686,838,861 'export':520,575,815 'fail':573,718,756,897 'failur':921 'file':1018 'fill':447,455,663 'filter':228,288,1022 'first':8,892 'fixtur':170,496,1015,1074,1133 'flake':1187 'flaki':84,1136 'fn':517,557,560,563,568 'fns':553 'fns.push':559 'fns.reverse':565 'follow':185,233 'free':1168 'fulli':328,789 'function':691,731 'getprojectidbynam':674,692 'global':265 'global/shared':784 'goal':97 'guardrail':245 'help':942 'highest':362 'host':969 'id':41,221,283,338,532,535,638,727,739,748,904 'id-bas':40,337 'ident':324 'implement':410 'import':426,499,505,614,694,734,769,811,836 'increas':1162 'index':900 'inject':975 'interfer':93 'isol':5,52,142,316,363,624,759,995 'isolationfixtur':513 'janitor':298,804,865,986 'job':987 'join':545 'keep':128,161,292,584,786,874,992 'larg':989 'least':1095 'level':364,405,1067 'lifo':597 'like':120,1023 'list':1123 'local':81,536,591 'login':433 'longer':1175 'lookup':717 'make':98,920 'mani':400 'manual':1200 'map':378 'mask':1035 'mechan':306 'membership':262 'migrat':1135 'mode':774,1160 'move':236,1152 'multipl':871 'must':184,782 'mutabl':318,1014,1137 'mutat':199,345,402,783,915,1090,1156 'name':463,585,655,662,668,699,708,710,819,826,903 'namespac':39,278,321,334,493,514,524,630,646,893,1073,1131 'nativ':414 'need':23,193 'negoti':181,244 'net':808 'never':226,257,284,1181 'new':656,714,752 'night':869,1041 'non':180,243,1184 'non-negoti':179,242 'non-test':1183 'note':579 'npx':956 'ns':538,548 'often':70 'older':354,846 'olderthanhour':859 'one':331,371,1096 'oper':104,946 'option':935 'orchestr':944 'order':341,595,601,1031 'org':210 'output':1048 'own':196,329,392,795 'ownership':38 'page':439,628 'page.context':468 'page.getbylabel':445,453,660 'page.getbyrole':461,653,666 'page.getbytext':687 'page.goto':441,648 'parallel':25,78,101,162,268,409,482,790,990,1005,1066,1084,1172 'parallel-saf':100,1083 'password':454,459 'path':470 'pattern':411,1002 'per':36,373,876 'per-test':35,875 'pick':360 'plan':1128 'playwright':2,7,49,59,109,122,163,413,962 'playwright-compat':961 'playwright-first':6 'playwright-n':412 'playwright-test-data-isol':1 'playwright.config.ts':480,810 'playwright/.auth/qa-user.json':471,621,825 'playwright/test':431,504,578,695,735,772,814,840 'pool':375 'prefix':270,857 'primari':304,881 'process.env':448,456,634 'process.env.base':442 'process.env.ci':530 'process.env.github':533 'produc':1069 'product':72 'production-connect':71 'project':423,477,483,581,625,657,661,716,754,805,818,902,1114,1148 'projectid':672,684 'projectnam':644,664,677,688 'promis':511 'propos':1108 'qa':436,1201 'qa/staging':22,64 'randomuuid':506,544 'rate':1188 'read':214,254,640 'read-on':213,253,639 'real':156 'recommend':307 'record':274 'refactor':1093 'regardless':997 'reli':1028 'remain':1124,1197 'remov':350 'report':1122 'reporting/ops':970 'request':629,675,682,693,733,850 'request.delete':744 'request.get':704 'request.post':854 'requir':27,1049 'res':702,742,852 'res.json':723 'res.ok':712,750,862 'res.status':719,757 'resourc':607,797 'retri':832,919,1033 'return':724,1118 'reus':416,484,1016 'revers':340,594 'reverse-ord':593 'risk':1121 'rollout':1127 'row':1027 'run':58,83,133,164,285,531,534,592,864,991,1169,1173 'runid':529,540,580 'runner':964,999 'safe':24,102,105,135,172,626,1085 'safeti':807 'schedul':294,348,868,1116,1150 'schedules/cloud':983 'serial':44,240,347,766,775,1159 'set':211,261,407,785 'setup':422,429,432,476 'shard':589 'share':11,13,46,63,68,74,124,137,206,247,250,259,310,322,343,383,420,435,609,636,762,912,930,976,1011,1013,1037,1062,1064,1088,1154,1178,1195 'shared-data':1036 'shared-st':45,342,761,929,1153 'shared-state-mut':1087 'short':1120 'sign':387,464,488 'sign-in':386 'signal':927 'signed-in':487 'skill':55,114,117,1052 'skill-playwright-test-data-isolation' 'source-stablyai' 'stabl':165,486,1198 'stabli':939,940,957,972,982 'stale':296,351,802,843,1111 'stale-data':295,801,1110 'standard':108 'state':47,344,418,490,763,931,1089,1155 'stay':107 'storagest':469,620,824 'strategi':9,359,949 'string':515,542,698,700,729,738,740 'success':934,1170 'suit':48,241,269,346,767,1091 'support':368 'target':1097 'task':119 'team':57,937 'teardown':43,291,602,1180 'test':3,14,19,37,50,60,75,82,85,92,99,123,143,152,183,195,225,231,272,290,326,332,391,401,427,500,522,615,622,764,770,780,788,794,837,841,877,888,898,908,958,1006,1081,1098,1126,1138,1157,1185,1194 'test-creat':271 'test-own':194,390,793 'test.describe.configure':773 'test.use':619 'testdir':821,828 'testinfo':527 'testinfo.project.name':541 'testinfo.workerindex':543 'tests/cleanup':829 'tests/cleanup/stale-e2e-data.spec.ts':835 'tests/e2e':822 'tests/e2e/projects.spec.ts':613 'tests/fixtures/test-data.ts':498 'threshold':357 'throw':713,751 'times/day':872 'tobetruthi':863 'tobevis':689 'toggl':266 'top':404 'top-level':403 'topic-agent-skills' 'touch':31,1182 'trackcleanup':516,549,631,678 'tracker':495,1078 'treat':205,246,923,1040 'tri':566 'true':760 'ts':424,497,612,768,809,834 'type':509,512 'uniqu':277,586 'unsaf':95,1125 'updat':258,1071 'url':443 'use':16,53,112,115,309,526,547,551,556,777,792,823,894,938,955,971,981,1020,1032 'user':76,248,450,458 'uuid':583 'via':333 'void':519 'vs':1086 'want':968 'wire':472 'without':30,154 'worker':374,582,830,899,984,1163 'workerindex':380 'workspac':209,251,260,637 'workspaceid':633,650,676,683,697,706,737,746 'wrapper':965 'write':397,1007","prices":[{"id":"622c952e-d96e-4262-9044-00d6d9237704","listingId":"ac95b61d-ee57-4555-a19c-954bdacafd41","amountUsd":"0","unit":"free","nativeCurrency":null,"nativeAmount":null,"chain":null,"payTo":null,"paymentMethod":"skill-free","isPrimary":true,"details":{"org":"stablyai","category":"agent-skills","install_from":"skills.sh"},"createdAt":"2026-05-18T13:22:55.911Z"}],"sources":[{"listingId":"ac95b61d-ee57-4555-a19c-954bdacafd41","source":"github","sourceId":"stablyai/agent-skills/playwright-test-data-isolation","sourceUrl":"https://github.com/stablyai/agent-skills/tree/main/skills/playwright-test-data-isolation","isPrimary":false,"firstSeenAt":"2026-05-18T13:22:55.911Z","lastSeenAt":"2026-05-18T19:15:07.076Z"}],"details":{"listingId":"ac95b61d-ee57-4555-a19c-954bdacafd41","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"stablyai","slug":"playwright-test-data-isolation","github":{"repo":"stablyai/agent-skills","stars":6,"topics":["agent-skills"],"license":null,"html_url":"https://github.com/stablyai/agent-skills","pushed_at":"2026-04-15T08:16:48Z","description":"AI agent skills for the Stably Playwright SDK.","skill_md_sha":"26abbbe4d6b998fe2d457797dc8b4fc739646b59","skill_md_path":"skills/playwright-test-data-isolation/SKILL.md","default_branch":"main","skill_tree_url":"https://github.com/stablyai/agent-skills/tree/main/skills/playwright-test-data-isolation"},"layout":"multi","source":"github","category":"agent-skills","frontmatter":{"name":"playwright-test-data-isolation","description":"Playwright-first strategy for shared DB + shared test accounts. Use when E2E\ntests collide in QA/staging, need safe parallelism, or require deterministic\ncleanup without touching baseline data. Covers per-test ownership,\nnamespacing, ID-based teardown, serial shared-state suites, and optional\nstale-data janitor jobs."},"skills_sh_url":"https://skills.sh/stablyai/agent-skills/playwright-test-data-isolation"},"updatedAt":"2026-05-18T19:15:07.076Z"}}