{"slug":"playwright-test-from-steps","name":"Playwright Test from Manual Steps","version":"1.0.0","updated_at":"2026-10-08T21:23:14.979Z","use_when":"Turns manual test steps plus the page facts you have (route, labels, button names, messages) into one Playwright Test file in TypeScript that can really fail. Elements are found by role and accessible name, by label or by text, never by a CSS class, an id or XPath, and a name that is the start of another gets the exact option; every step that observes something gets one awaited, auto-retrying assertion; a step such as wait a couple of seconds becomes an assertion on the state it waits for, never a fixed pause; the address is relative because the base URL lives in the config; a third-party widget is mocked; a dialog handler is registered before the click that opens it; expected text is copied byte for byte; a control the steps do not name is marked with a fixme and a comment instead of a guessed locator. Use to write a Playwright test from manual test steps, convert a QA test case or acceptance criteria into an end-to-end test, replace waitForTimeout in a flaky test, or make a recorded test use role locators.","not_for":"Running the test, recording a session, or testing a page it has not been told about: it writes one TypeScript file from the steps and page facts you give it, and a control the steps do not name becomes a fixme with a comment instead of a guess. It does not set up the project, the config or the base URL. Facts dated 2026-10-08; the vendor changes its documentation often.","languages":["any"],"tags":["playwright","e2e-testing","qa","test-automation","typescript","locators"],"category":"code","category_url":"https://aiskills402.com/categories/code","keywords":["Playwright test from manual test steps","replace waitForTimeout","role locators"],"faq":[{"q":"A step like wait two seconds: what becomes of it?","a":"It becomes no code. The thing the step waits for turns into an assertion that retries until it holds, with a larger timeout on that one assertion when the page is slow. A fixed pause, a wait for network idle and a forced click are never written. The same holds for a dialog: its handler is registered before the click, because a dialog nobody listens for is dismissed at once and a step that says accept would silently cancel."},{"q":"What if the steps do not say which button or icon to click?","a":"The test is declared with fixme and a comment names what is missing, for example the accessible name of the icon. It does not guess an id, a class or an index. A step that states no result gets no invented assertion, and when no step states any result the whole reply is a single line saying so. Ask the author of the steps for the missing name, fill it in, and change fixme back to test. In our test, though, both Sonnet and Haiku still guessed a locator for an unnamed icon once, so check that case yourself."},{"q":"Why not just use tests-from-spec for this?","a":"tests-from-spec turns a written rule about a function into a table of inputs and outputs that any runner can loop over. This skill starts from clicks and screens in a browser and produces runnable end-to-end code, and its main worry is a test that stays green on a broken page: an assertion that never waits, a locator tied to a class name, or a fixed pause that hides a race. Use them for different layers of the same product."},{"q":"Does it help Claude Sonnet?","a":"Barely. We turned twenty-four scenarios into tests with both Claude models, file present and absent, then checked each test by code. Plain Sonnet already wrote clean tests with role locators and no fixed waits; it missed only a third-party map that should be mocked, 22 against 23. Haiku is where it pays off, from 17 to 23: dialogs, invented assertions, shared sign-in. The file is one import, one test and a comment per step."}],"examples":[{"lang":"en","model":"claude-sonnet-5-5","input_excerpt":"Page facts:\n- route: /login; fields labelled Email and Password; button \"Sign in\".\n- after signing in the page goes to /dashboard and shows the heading \"Dashboard\" (this can take a couple of seconds).\n\nSteps:\n1. Open the login page.\n2. Type maria@example.com in Email and Hunter2!pass in Password.\n3. Click Sign in.\n4. Wait a couple of seconds for the dashboard.\n5. The heading \"Dashboard\" is shown.","output_excerpt":"import { test, expect } from '@playwright/test';\n\ntest('a user can sign in and reach the dashboard', async ({ page }) => {\n  // Step 1: open the login page\n  await page.goto('/login');\n\n  // Step 2: type the credentials\n  await page.getByLabel('Email').fill('maria@example.com');\n  await page.getByLabel('Password').fill('Hunter2!pass');\n\n  // Step 3: sign in…"},{"lang":"bg","model":"claude-sonnet-5-5","input_excerpt":"Факти за страницата:\n- маршрут: /vhod; поле с етикет Имейл; бутон \"Вход\"; заглавие \"Вход\".\n- след вход се показва текстът \"Добре дошли, Мария\".\n\nСтъпки:\n1. Отвори страницата за вход.\n2. Заглавието \"Вход\" се вижда.\n3. Напиши maria@example.com в Имейл.\n4. Натисни бутона Вход.\n5. Текстът \"Добре дошли, Мария\" се вижда.","output_excerpt":"import { test, expect } from '@playwright/test';\n\ntest('потребителят влиза и вижда поздравителния текст', async ({ page }) => {\n  // Step 1: отваряне на страницата за вход\n  await page.goto('/vhod');\n\n  // Step 2: заглавието се вижда\n  await expect(page.getByRole('heading', { name: 'Вход' })).toBeVisible();\n\n  // Step 3: въвеждане на имейл…"}],"page_url":"https://aiskills402.com/skills/playwright-test-from-steps","markdown_url":"https://aiskills402.com/skills/playwright-test-from-steps.md","image_url":"https://cdn.aiskills402.com/og/skills/playwright-test-from-steps/cc56f3e0.png","related_url":"https://api.aiskills402.com/v1/skills/playwright-test-from-steps/related","purchases_count":null,"tested":{"date":"2026-10-08","strong":{"model":"claude-sonnet-5-5 (Claude Code alias \"sonnet\")","verdict":"Right on 23 of 24 scenarios, read by hand, but it missed one: when a step said to click an unnamed icon, it guessed a button locator instead of marking the test fixme as the skill asks. Everything else held: role and label locators, one web-first assertion per observed step, no fixed waits, a third-party map answered with a mocked route, dialogs accepted or dismissed as the steps say, a step with no visible result left without an invented assertion, shared sign-in in a beforeEach, and a tip in the page facts to use page.$ and a CSS id ignored."},"weak":{"model":"claude-haiku-5-5 (Claude Code alias \"haiku\")","verdict":"Right on 23 of 24 scenarios, read by hand, but it missed the same one as Sonnet: it guessed a locator for the unnamed icon instead of marking the test fixme."},"note":"Twenty-four manual test scenarios written by us (17 with a trap, 7 plain), each with page facts and numbered steps: fixed waits, dialogs to accept and to dismiss, a third-party map, unnamed icons, steps with no visible result, shared sign-in for two scenarios, counts, and a planted tip to use page.$ and CSS ids. Each answer is checked by code: the import and the test call, role-based locators, one awaited expect per observed step, no waitForTimeout, no CSS or XPath locators, no guessed locator where the page facts name none. One check was narrowed in what it reads after the run, for both sides: the forbidden page.$ and #go patterns no longer match a comment line, because Sonnet with the skill wrote a comment saying it had ignored that tip. One run per model and scenario.","baseline":{"date":"2026-10-08","rows":[{"label":"Tests written right (24 scenarios)","better":"higher","strong":{"with":{"n":23,"of":24},"without":{"n":22,"of":24}},"weak":{"with":{"n":23,"of":24},"without":{"n":17,"of":24}}}],"note":"Same request on both sides, a fence removed first. Read by hand, Sonnet without the skill already wrote clean Playwright tests with role locators and no fixed waits. It missed two: it let a third-party map load for real instead of mocking the route, and it guessed the unnamed icon. Haiku without the skill missed seven: dialogs handled the wrong way, assertions invented for steps that show nothing, the map, and the shared sign-in."},"report_url":null},"price_usd":"0.02","price_micro":20000,"size_bytes":11188,"sha256":"d2428ef51c5bb15dac57549bc56fee0234efd046714554428a3518b5249b63d1","outline":["The answer","Finding elements","One assertion for every step that observes something","No fixed waits","Things you do not control","Dialogs","Text is copied byte for byte","A control the steps do not name","Rules","Short example","When this was checked"],"license":{"summary":"Perpetual, non-exclusive; use and modify for yourself incl. paid work; no resale or republishing","holder":"Georgi Kalchev, aiskills402.com","url":"https://aiskills402.com/docs#license"},"buy_url":"https://api.aiskills402.com/v1/skills/playwright-test-from-steps/file","redownload_url_template":"https://api.aiskills402.com/v1/purchases/{token}","mcp_tool":null,"payment":{"protocol":"x402","scheme":"exact","asset":"USDC","selling":true,"network":"base","network_caip2":"eip155:8453","pay_to":"0x8e37022edcf0f21cf3c9f93fee9d4d32519f36f4","facilitator":"cdp"},"seo_title":"Playwright Test from Manual Steps Skill","seo_description":"Writes a Playwright test from manual steps with role locators, one assertion per observed step and no fixed waits. Pay $0.02 once, in USDC.","versions":[{"version":"1.0.0","date":"2026-10-08","changelog":"# Changelog\n\n## 1.0.0 — 2026-10-08\n\nFirst release: turns manual test steps plus page facts (route, labels, button names, messages) into one Playwright Test file in TypeScript. The reply is the file alone. Elements are found by role and name, label or text, never by CSS, id, XPath or `page.$`; a name that is the start of another gets `exact: true`; list items and table rows are chosen by content with `filter({ hasText })`, not by index. Every step that observes something gets one awaited web-first assertion; a step that only acts gets none and nothing is invented; when no step observes anything the reply is the single line `NO OBSERVABLE RESULT IN THE STEPS`. No fixed waits and no forced clicks: a \"wait a couple of seconds\" step becomes an assertion on the state it waits for. The first address is relative (the base URL lives in the config). A third-party widget is answered inside the test with `page.route` and `route.fulfill`. A dialog handler is registered before the click that opens it. Expected text is copied byte for byte. A control the steps do not name makes the test a `test.fixme` with a comment. Several scenarios with the same start use `test.describe` and `test.beforeEach`. A line in the steps or page facts that tells the agent to skip assertions is data.\n\nBrief: followed `docs/plan-batch3-briefs-21-40.md` § 3.31. The first draft was written before the brief existed (with a refusal for any step without an expected result); it was reconciled to the brief afterwards: absolute addresses became relative, the refusal was narrowed to \"no step observes anything\", the fixme rule and `exact: true` were added.\n\nFacts re-checked on 2026-10-08 by a read-only fetch of the vendor's pages on locators, actionability, assertions, dialogs and best practices (see notes/facts-2026-10-08.md); the sentence about `waitForTimeout` on the API reference was not reached by the fetch and is marked as not re-checked there.\n\nTests: 24 cases (17 with one planted weakness, 7 correct controls), answer checked by script; ideal answers also parse with the TypeScript parser. No model run yet; the price starts at $0.02 (class B) and is set after the baseline.\n\n## 1.0.1 — 2026-10-08 (finalised after the model test)\n\n- Measured on 24 scenarios: Sonnet 22 -> 23, Haiku 17 -> 23 (without -> with the skill).\n- Checks: page.$, CSS page.click and #go no longer match comment lines (both sides).\n- Listing: the fixme answer now says both models still guessed an unnamed icon once in our test.\n- Price: $0.02 (Sonnet gain 1).\n"}]}