Build a Playwright Quality Gate
Create a TypeScript Playwright suite with CI reports and cross-browser checks.
Introduction
30 Second Summary
A website can look fine during a quick check. A small change can still break the exact action someone depends on.
In this project, you will build a Playwright Test suite in TypeScript that tests a visible TodoMVC journey. You will publish the same suite as a GitHub Actions quality gate that checks every push or pull request.
What You'll Build
The finished suite uses Chromium to complete a TodoMVC journey while the terminal confirms the result.
By the end of this project, you'll have:
- A visible Chromium run that adds Review pull request before marking it complete.
- A strict locator diagnosis that exposes why one checkbox action matches two tasks. An inspectable trace then shows the scoped fix step by step.
- A public CI quality gate that reruns the suite in GitHub Actions on every push or pull request. Each run publishes a downloadable HTML report.
- Secret Mission: Run the same two tests across Chromium, Firefox, and WebKit without duplicating the test file.
Are there any prerequisites?
Your starting point is a Windows 11 computer with VS Code installed.
You also need internet access plus permission to install software. The guide helps you set up everything else.
Before We Start
Before the hands-on work begins, commit to the quality gate you are about to build. You will turn visible TodoMVC behavior into automated browser evidence that demonstrates your test engineering skills.
Set Up Windows for Playwright
Your future Playwright suite needs a supported JavaScript runtime before it can launch browser tests. Node.js provides that runtime. The npm command supplies the project packages you use in the next step.
Git records each change you make to the quality gate. GitHub connects those commits to a verified account. Together, these tools give your browser evidence a trustworthy history.
In this step, get ready to:
- Install Node.js 24.21.0 LTS with npm.
- Prepare Git for Windows 2.55.0(5) with a verified GitHub account.
- Confirm every tool from a restarted Visual Studio Code terminal.
Prepare the JavaScript runtime
A PowerShell version check tells you whether Windows can find Node.js. You may see the required release, an older release, or a message that the command is unavailable.
- Press the Windows key to open Windows search.
- Type PowerShell into the search field.
- Press Enter to open PowerShell.
- Check the available Node.js version by running this command:
node --version
What does this check prove?
The node --version command asks the active shell which Node.js executable it can find. The result identifies the setup path that matches your computer.
✔️ I see v24.21.0
Your terminal already uses the required Node.js LTS release. The matching npm installation is ready for the final check.
- Keep this PowerShell window available for the Git check.
ⓧ I see an older version
An older Node.js release can cause package compatibility failures. Install the required LTS release before creating the Playwright project.
- Download the Node.js 24.21.0 LTS Windows installer from the official Node.js download page.
- Choose the Windows installer that matches your computer's architecture.
- Run the downloaded installer.
- Approve the Windows permission request if it appears.
- Keep npm included as part of the Node.js installation.
- Complete the installer prompts.
ⓧ Command not found
Windows cannot find Node.js yet. The official Windows installer adds the runtime plus npm.
- Download the Node.js 24.21.0 LTS Windows installer from the official Node.js download page.
- Choose the Windows installer that matches your computer's architecture.
- Run the downloaded installer.
- Approve the Windows permission request if it appears.
- Keep npm included as part of the Node.js installation.
- Complete the installer prompts.
A fresh PowerShell session discovers the newly installed command.
- Close the current PowerShell window.
- Press the Windows key to reopen Windows search.
- Type PowerShell into the search field.
- Press Enter to open a fresh PowerShell window.
- Confirm the installed Node.js release by running this command:
node --version
What should this command show?
PowerShell should print v24.21.0. That output proves Windows can find the required Node.js LTS release.
You have cleared the first setup hurdle. The supported runtime is now available to your terminal.
Still unable to find Node.js?
- Confirm that the downloaded Windows installer matches your computer's architecture.
- Close every PowerShell window after the installer completes.
- Reopen PowerShell before checking the version again.
- Help me troubleshoot my Node.js installation.
Prepare version control and your GitHub identity
Git gives each project change a permanent record. Checking the installed version first keeps the setup path clear for computers that already have Git.
- Check whether PowerShell can find Git by running this command:
git --version
What does this check prove?
The git --version command asks the active shell for the installed Git release. Your result determines whether you can continue or need the current Git for Windows installer.
✔️ I see version 2.55.0
Git for Windows is already on the required release. Keep PowerShell available for the account setup that follows.
ⓧ I see an older version
The installed Git release needs an update before you create the repository history.
- Download the current setup program from the official Git for Windows page.
- Choose the setup program that matches your computer's architecture.
- Run the downloaded setup program.
- Approve the Windows permission request if it appears.
- Complete the setup prompts.
ⓧ Command not found
Windows cannot find Git yet. Windows Package Manager can download the maintained Git for Windows package.
What might Windows ask?
PowerShell may pause for package source agreements. Windows may also show an installation permission request.
- Accept any package source agreements that PowerShell requests.
- Approve the Windows permission request if it appears.
- Install Git for Windows by running this command:
winget install --id Git.Git -e --source winget
What does this command do?
- The winget install command asks Windows Package Manager to install software.
- The --id Git.Git option selects the Git for Windows package.
- The -e option requires an exact package identifier.
- The --source winget option uses the Windows Package Manager source.
PowerShell downloads the package before installing Git for Windows. The command finishes with an installation confirmation.
A fresh shell session now needs to discover the Git command.
- Close the current PowerShell window.
- Press the Windows key to reopen Windows search.
- Type PowerShell into the search field.
- Press Enter to open a fresh PowerShell window.
- Confirm the installed Git release by running this command:
git --version
What should this command show?
PowerShell should print a Git version line containing 2.55.0. That line proves the version-control command is available.
Still unable to find Git?
- Confirm that the Git installation reached its completion message.
- Close every PowerShell window before checking the version again.
- Help me troubleshoot my Git installation.
GitHub asks you to create login credentials for the account that hosts your repository. Your password stays outside this project.
- Open the GitHub signup page in your browser.
- Create a free personal account by following the on-screen prompts.
- Open the verification email from GitHub.
- Verify the account through the link in that email.
You now have a verified GitHub identity ready to host the portfolio repository you publish later.
Confirm the complete toolchain
Restarting VS Code lets its integrated terminal discover the newly installed commands. The fresh terminal also gives you one place to configure your commit identity before the final check.
- Close every VS Code window.
- Press the Windows key to open Windows search.
- Type Visual Studio Code into the search field.
- Press Enter to restart VS Code.
- Use the terminal controls in VS Code's top menu to create a PowerShell terminal.
Every Git commit carries an author name plus an email address. Global configuration lets Git reuse that identity for the repositories you create on this computer.
- Replace YOUR NAME with the name you want attached to your commits.
- Replace YOUR_EMAIL with the verified email associated with your GitHub account.
- Set both global identity values by running these commands:
git config --global user.name "YOUR NAME"
git config --global user.email "YOUR_EMAIL"
What do these commands store?
- The --global option applies the identity to future repositories for your Windows user.
- The user.name value becomes the author name stored with your commits.
- The user.email value connects those commits to the email verified on GitHub.
Before you run the final check, what output would prove that each tool is available from the restarted editor?
- Verify the complete Windows toolchain by running these commands:
node --version
npm help
git --version
What should you see?
- The node --version command prints v24.21.0.
- The npm help command prints npm's command help.
- The git --version command prints a Git version line containing 2.55.0.
Your Windows toolchain is ready. Node.js, npm, Git, VS Code, and your verified GitHub identity can now support the quality gate.
One of the commands still unavailable?
- Close every VS Code window before opening the editor again.
- Confirm that both installers completed successfully.
- Run the three verification commands in a new VS Code PowerShell terminal.
- Help me fix my Windows toolchain check.
The supported runtime, package manager, version control, and repository identity are in place. Next, you create the project files and watch Chromium complete your first visible TodoMVC test.
Create Your First Visible Todo Test
Your Windows toolchain is ready inside VS Code. The next milestone is proof you can watch.
Playwright Test will drive Chromium through one TodoMVC journey. You will write the test in TypeScript so each browser action stays readable.
In this step, get ready to:
- Create the local project structure with its package settings.
- Install Playwright Test and its Chromium browser binary.
- Run an add-todo test in a visible browser window.
Create the project foundation
A small manual project keeps every file connected to the quality gate. This gives you a clean starting point before the test suite grows.
- Create the C:\Projects\playwright-todo-quality-gate folder from the PowerShell terminal by running:
New-Item -Path "C:\Projects" -ItemType Directory -Force
New-Item -Path "C:\Projects\playwright-todo-quality-gate" -ItemType Directory -Force
What do these commands do?
- The first command ensures that C:\Projects exists.
- The second command creates the playwright-todo-quality-gate folder inside it.
- The -Force option lets PowerShell keep going when a named folder already exists.
- Click File in the VS Code top menu.
- Select Open Folder.
- Choose C:\Projects\playwright-todo-quality-gate.
- Click Select Folder.
- Approve the folder trust prompt if VS Code displays one.
- Click Terminal in the top menu.
- Select New Terminal.
The new PowerShell prompt should end with C:\Projects\playwright-todo-quality-gate. The Explorer sidebar should show the same folder name.
Folder missing or terminal in the wrong place?
- Confirm that both PowerShell commands use the C:\Projects drive path.
- Reopen C:\Projects\playwright-todo-quality-gate as the active VS Code folder.
- Create a new integrated terminal after the folder opens.
- Help me open the correct project folder in VS Code.
The package manifest defines the project identity. It also pins the test dependency so every installation uses the same release.
- Click the New File button in the VS Code Explorer sidebar.
- Enter package.json as the file name.
- Paste this package manifest into package.json:
{
"name": "playwright-todo-quality-gate",
"version": "1.0.0",
"private": true,
"description": "A Playwright TypeScript quality gate for TodoMVC.",
"scripts": {
"test": "playwright test",
"test:headed": "playwright test --headed",
"report": "playwright show-report"
},
"devDependencies": {
"@playwright/test": "1.63.0"
}
}
What does this file define?
- The scripts section gives the project short commands for normal tests, visible tests, and reports.
- The devDependencies section pins @playwright/test to 1.63.0.
- The private setting protects this portfolio project from accidental publication as an npm package.
- Save package.json.
- Confirm that package.json appears in the Explorer sidebar.
Seeing JSON warnings?
- Check that every property except the last property in an object ends with a comma.
- Confirm that the file starts with { and ends with }.
- Help me find the JSON syntax problem.
Browser tests generate dependencies and diagnostic output. A Git ignore file keeps those generated folders out of the repository.
- Click the New File button in the Explorer sidebar.
- Enter .gitignore as the file name.
- Paste these exclusions into .gitignore:
node_modules/
playwright-report/
test-results/
What does this file exclude?
These entries exclude installed packages and generated test evidence. Your repository stays focused on the files another developer needs to reproduce the quality gate.
- Save .gitignore.
- Confirm that .gitignore appears beside package.json in the Explorer sidebar.
Install Playwright and Chromium
The package manifest describes the required dependency. npm now turns that description into an installed package tree with a generated lockfile.
- Generate the locked dependency installation from package.json by running:
npm install
What does this command create?
- npm reads the dependency pinned in package.json.
- The installation stores the downloaded packages inside node_modules.
- npm generates package-lock.json with the exact resolved dependency tree.
- Confirm that the PowerShell prompt returns without an installation error.
- Confirm that package-lock.json appears in the Explorer sidebar.
Dependency installation failed?
- Confirm that the terminal prompt shows C:\Projects\playwright-todo-quality-gate.
- Check that package.json is saved before retrying the installation.
- Help me diagnose the npm installation failure.
Playwright Test needs a matching browser binary before it can launch Chromium. The download can take a few minutes on a slower connection.
- Download the Chromium browser binary by running:
npx playwright install chromium
What does this command install?
The command downloads the Chromium build managed by the installed Playwright release. This gives the test runner a consistent browser for local execution.
- Wait for the download to finish.
- Confirm that the PowerShell prompt returns without a browser installation error.
Browser download stopped?
- Confirm that your internet connection is active.
- Retry the browser installation from the project terminal.
- Help me diagnose the Chromium download problem.
Before you check the installation, predict whether the project will report the version pinned in package.json.
- Check the installed Playwright version by running:
npx playwright --version
What does this check prove?
The command asks the project installation for its Playwright version. It confirms that PowerShell can find the package through the current project.
Use the tab that matches your terminal output.
✔️ I see the expected version
You should see Version 1.63.0. Your test runner matches the project configuration.
ⓧ I see an older version
An older version means the installed dependency does not match the manifest. The pinned dependency should be @playwright/test version 1.63.0.
- Confirm that package.json contains the pinned dependency.
- Refresh the installation by running these commands:
npm install
npx playwright --version
What do these commands check?
The first command resolves the dependency from the saved manifest. The second command reports the resulting Playwright version.
You should now see Version 1.63.0.
ⓧ Command not found
The project installation may be incomplete. A fresh terminal also ensures that VS Code uses the current workspace.
- Close the current integrated terminal with its trash button.
- Select New Terminal from the Terminal menu.
- Restore the dependency installation by running these commands:
npm install
npx playwright --version
What do these commands restore?
The installation command restores the project dependency. The version command checks that Playwright is available from the new terminal.
You should now see Version 1.63.0.
Configure and run the test
The test runner needs a small configuration before it can interpret relative page addresses. The same file also keeps execution predictable with one worker and an HTML report.
- Click the New File button in the Explorer sidebar.
- Enter playwright.config.ts as the file name.
- Paste this configuration into playwright.config.ts:
import { defineConfig } from '@playwright/test';
export default defineConfig({
testDir: './tests',
workers: 1,
reporter: [['html', { open: 'never' }]],
use: {
baseURL: 'https://demo.playwright.dev/todomvc/',
},
});
What does this configuration do?
- The testDir setting tells Playwright to find tests inside tests.
- The workers setting keeps this first run sequential.
- The reporter creates an HTML report without opening it automatically.
- The baseURL setting lets the test navigate with the relative path /.
- Save playwright.config.ts.
- Confirm that playwright.config.ts appears in the Explorer sidebar.
Configuration import underlined?
- Confirm that npm install completed successfully.
- Check that the import uses @playwright/test.
- Help me fix the Playwright configuration import.
The first test follows a journey a person can recognize. It enters one task and checks the two visible results that prove the page accepted it.
- Click the New Folder button in the Explorer sidebar.
- Enter tests as the folder name.
- Select the tests folder in the Explorer sidebar.
- Click the New File button.
- Enter todo.spec.ts as the file name.
- Paste this test into tests/todo.spec.ts:
import { test, expect } from '@playwright/test';
test.beforeEach(async ({ page }) => {
await page.goto('/');
});
test('adds a todo', async ({ page }) => {
const input = page.getByPlaceholder('What needs to be done?');
await input.fill('Review pull request');
await input.press('Enter');
await expect(page.getByText('Review pull request', { exact: true })).toBeVisible();
await expect(page.getByText('1 item left')).toBeVisible();
});
What does this test prove?
- The beforeEach hook starts every test from the TodoMVC page.
- The placeholder locator finds the task input through text visible to a user.
- The test fills the input with Review pull request.
- The Enter key submits the new task.
- The assertions wait for the task text and remaining-item counter to become visible.
- Save tests/todo.spec.ts.
Before you run the test, do you expect Chromium to display the new task before the terminal reports success?
- Run the focused test in a visible Chromium window by entering:
npx playwright test --headed -g "adds a todo"
What does this command select?
- The test command starts the Playwright test runner.
- The --headed option keeps browser activity visible during execution.
- The -g filter selects the test named adds a todo.
You will see Chromium open TodoMVC and add Review pull request. The terminal then finishes with one passing test.
Test did not pass?
- Confirm that the terminal prompt shows C:\Projects\playwright-todo-quality-gate.
- Check that tests/todo.spec.ts contains the exact task text Review pull request.
- Confirm that the Chromium installation completed before retrying the test.
- Help me diagnose the failing visible test.
✔️ Awesome, I've got everything!
Your project now has all four hand-authored files. It also has the generated package-lock.json needed for reproducible dependency installation.
- Confirm that every file is saved.
- Confirm that the focused test finishes with one passing result.
ⓧ I'd like to double check the full code
Compare each saved file with the complete versions below.
{
"name": "playwright-todo-quality-gate",
"version": "1.0.0",
"private": true,
"description": "A Playwright TypeScript quality gate for TodoMVC.",
"scripts": {
"test": "playwright test",
"test:headed": "playwright test --headed",
"report": "playwright show-report"
},
"devDependencies": {
"@playwright/test": "1.63.0"
}
}
node_modules/
playwright-report/
test-results/
import { defineConfig } from '@playwright/test';
export default defineConfig({
testDir: './tests',
workers: 1,
reporter: [['html', { open: 'never' }]],
use: {
baseURL: 'https://demo.playwright.dev/todomvc/',
},
});
import { test, expect } from '@playwright/test';
test.beforeEach(async ({ page }) => {
await page.goto('/');
});
test('adds a todo', async ({ page }) => {
const input = page.getByPlaceholder('What needs to be done?');
await input.fill('Review pull request');
await input.press('Enter');
await expect(page.getByText('Review pull request', { exact: true })).toBeVisible();
await expect(page.getByText('1 item left')).toBeVisible();
});
The generated package-lock.json should also be present. npm created it from the playwright-todo-quality-gate package with @playwright/test version 1.63.0.
That is your first visible browser proof complete. Next, you will add a second journey that exposes an ambiguous locator.
Trigger a Strict Locator Failure
Your first Playwright Test journey already opens TodoMVC. It proves that a user can add a task.
A locator that seems precise with one task can become ambiguous when the page contains two matching controls. This step lets that weakness surface in a controlled test so you can read the evidence.
In this step, get ready to:
- Create two todos inside a new completion test.
- Run the focused test to inspect how Playwright evaluates the checkbox locator.
Add a second todo journey
The new test needs two visible tasks so the checkbox locator faces a realistic choice. A for...of loop creates both tasks through the same user journey.
- In the VS Code Explorer sidebar, select tests/todo.spec.ts.
- Scroll below the existing adds a todo test.
- Place the following test at the end of tests/todo.spec.ts:
test('completes the selected todo', async ({ page }) => {
const input = page.getByPlaceholder('What needs to be done?');
for (const task of ['Review pull request', 'Update test plan']) {
await input.fill(task);
await input.press('Enter');
}
await page.getByRole('checkbox', { name: 'Toggle Todo' }).check();
});
What Does This Test Set Up?
- The input locator reuses the visible placeholder from the add-todo journey.
- The for...of loop repeats that journey for each task name.
- The checkbox locator searches by its user-facing role. Its accessible name is Toggle Todo.
- The check() action asks Playwright to select the locator's result.
- Save tests/todo.spec.ts.
- Verify that tests/todo.spec.ts now shows the adds a todo test followed by the completes the selected todo test.
Good progress. Your suite now has the two-task scenario needed to challenge the locator.
Seeing an Editor Error?
- Check that every opening brace has a matching closing brace.
- Confirm that the new test sits outside the existing adds a todo test.
- Help me compare the new test with the target code.
✔️ Awesome, I've got everything!
Your saved tests/todo.spec.ts now contains both test cases in the correct order.
ⓧ I'd like to double check the full code
- Compare tests/todo.spec.ts with this complete reference:
import { test, expect } from '@playwright/test';
test.beforeEach(async ({ page }) => {
await page.goto('/');
});
test('adds a todo', async ({ page }) => {
const input = page.getByPlaceholder('What needs to be done?');
await input.fill('Review pull request');
await input.press('Enter');
await expect(page.getByText('Review pull request', { exact: true })).toBeVisible();
await expect(page.getByText('1 item left')).toBeVisible();
});
test('completes the selected todo', async ({ page }) => {
const input = page.getByPlaceholder('What needs to be done?');
for (const task of ['Review pull request', 'Update test plan']) {
await input.fill(task);
await input.press('Enter');
}
await page.getByRole('checkbox', { name: 'Toggle Todo' }).check();
});
This reference preserves the passing add-todo test. It adds the two-task completion scenario beneath it.
Run the ambiguous locator
Playwright applies strictness when an action needs one element. The focused run reveals whether this checkbox locator identifies a unique target.
Before you run the focused test, do you think check() can choose one task when both checkboxes share the same accessible name?
- Run only the completion test by entering this command in the terminal from earlier:
npx playwright test -g "completes the selected todo"
What Did Playwright Prove?
The focused test should fail with a strictness violation. This is the intended result for this step.
- The loop creates two list items with the accessible checkbox name Toggle Todo.
- The check() action requires one target.
- The checkbox locator resolves to two elements.
- Scroll through the terminal output to the call log.
- Count the checkbox matches reported for the failed action.
- Keep the broad locator unchanged without adding first() or nth().
That's the failure you were aiming for. The suite has exposed an ambiguity that a one-item page kept hidden.
The first() and nth() methods select by position. A reliable locator should express which todo the user means.
Test Passing Unexpectedly?
- Confirm that the loop still contains Review pull request.
- Confirm that the loop also contains Update test plan.
- Check that the checkbox action still uses getByRole('checkbox', { name: 'Toggle Todo' }) without extra filtering.
- Help me understand why the strictness check did not fail.
You have reproduced a deterministic locator failure and used its call log as evidence. Next, you'll narrow the target to the intended todo before adding diagnostics for future failures.
Fix the Locator and Add Diagnostics
You have already seen a broad checkbox action fail when two matching controls appeared. That failure showed how Playwright Test uses strictness to expose ambiguous test actions.
A text-filtered locator can identify the intended todo through the words a user sees.
Traces preserve the steps behind each run. An HTML report turns that evidence into an interface you can inspect.
In this step, get ready to:
- Scope the completion action to the todo with visible text.
- Configure retry-based diagnostics with failure screenshots.
- Inspect a passing two-test run through its HTML report.
Scope the checkbox locator
A scoped locator starts with the list item whose visible text identifies the task. The checkbox search then stays inside that one item.
- Switch back to tests/todo.spec.ts in VS Code.
- Locate the broad checkbox action below the task loop.
- Replace that action by pasting this scoped locator with its assertions:
const reviewTodo = page
.getByRole('listitem')
.filter({ hasText: 'Review pull request' });
const reviewToggle = reviewTodo.getByRole('checkbox', { name: 'Toggle Todo' });
await reviewToggle.check();
await expect(reviewToggle).toBeChecked();
await expect(page.getByText('1 item left')).toBeVisible();
What does this code do?
- The reviewTodo locator filters the list items by the visible text Review pull request.
- The reviewToggle locator searches for the checkbox named Toggle Todo within that filtered item.
- The check() action completes the selected todo.
- The toBeChecked() assertion verifies the checkbox state.
- The final assertion confirms that one incomplete item remains.
- Save tests/todo.spec.ts.
Before you rerun the suite, do you expect the completion test to find one checkbox or two?
- Verify the corrected locator in the PowerShell terminal from earlier by running this command:
npx playwright test
What does this command prove?
The command runs every test in the configured test directory. A passing completion test proves that the filtered locator resolves to one checkbox.
You should see two tests pass in the terminal. That is the strictness gap closed: the completion test now targets one checkbox.
Still seeing a strictness violation?
- Confirm that the original broad checkbox action has been removed from tests/todo.spec.ts.
- Check that reviewToggle comes from reviewTodo.
- Check that the filter text matches Review pull request exactly.
- Help me debug the remaining strictness violation.
Configure retry diagnostics
A retry gives a failed test one more execution. Recording options preserve a screenshot or trace when that extra evidence matters.
- Switch back to playwright.config.ts.
- Add retries: 1, below workers: 1,.
- Add the screenshot and trace settings below the existing baseURL setting.
- Compare the updated diagnostics section with this reference:
workers: 1,
retries: 1,
reporter: [['html', { open: 'never' }]],
use: {
baseURL: 'https://demo.playwright.dev/todomvc/',
screenshot: 'only-on-failure',
trace: 'on-first-retry',
},
How do these diagnostics work?
- The retries setting reruns a failed test once.
- The screenshot setting captures the page when a test fails.
- The trace setting records the first retry after a failure.
- The HTML reporter stores the run in playwright-report without opening it automatically.
- Save playwright.config.ts.
Before you record the complete run, do you expect the fixed suite to finish with a failure or two passing tests?
- Record traces for both tests by running this command:
npx playwright test --trace on
What does trace mode capture?
The --trace on option records a trace for each test in this run. The HTML reporter links those traces to the corresponding results.
You should see both tests pass. The latest HTML report now contains traces you can inspect.
Did the traced run fail?
- Check that each new setting ends with a comma in playwright.config.ts.
- Confirm that trace sits inside the use object.
- Confirm that the filtered locator changes are saved in tests/todo.spec.ts.
- Help me debug my failed traced run.
✔️ Awesome, I've got everything!
Your two tests pass with the filtered checkbox locator. Your saved configuration now contains the retry and recording options.
ⓧ I'd like to double check the full code
Your completed tests/todo.spec.ts should match this file:
import { test, expect } from '@playwright/test';
test.beforeEach(async ({ page }) => {
await page.goto('/');
});
test('adds a todo', async ({ page }) => {
const input = page.getByPlaceholder('What needs to be done?');
await input.fill('Review pull request');
await input.press('Enter');
await expect(page.getByText('Review pull request', { exact: true })).toBeVisible();
await expect(page.getByText('1 item left')).toBeVisible();
});
test('completes the selected todo', async ({ page }) => {
const input = page.getByPlaceholder('What needs to be done?');
for (const task of ['Review pull request', 'Update test plan']) {
await input.fill(task);
await input.press('Enter');
}
const reviewTodo = page
.getByRole('listitem')
.filter({ hasText: 'Review pull request' });
const reviewToggle = reviewTodo.getByRole('checkbox', { name: 'Toggle Todo' });
await reviewToggle.check();
await expect(reviewToggle).toBeChecked();
await expect(page.getByText('1 item left')).toBeVisible();
});
What should this file contain?
The first test adds one todo. The second test filters the intended list item before checking its checkbox.
Both tests verify visible behavior through auto-retrying assertions.
Your completed playwright.config.ts should match this file:
import { defineConfig } from '@playwright/test';
export default defineConfig({
testDir: './tests',
workers: 1,
retries: 1,
reporter: [['html', { open: 'never' }]],
use: {
baseURL: 'https://demo.playwright.dev/todomvc/',
screenshot: 'only-on-failure',
trace: 'on-first-retry',
},
});
What should this config contain?
The configuration runs one worker with one retry. It also enables failure screenshots plus a trace on the first retry.
The HTML reporter remains available after each run without opening automatically.
Inspect the HTML report
The passing run generated a local report with a trace for each test. The trace connects each action to its source code plus the browser activity captured at that moment.
Before you open the report, where do you expect the filtered checkbox action to appear in the trace?
- Open the latest HTML report by running this command:
npx playwright show-report
What does this command do?
The command starts a local report server. It opens the latest report in your browser.
Keep the PowerShell terminal running while you inspect the report. Press Ctrl+C when you finish.
- Open the trace for completes the selected todo from the report.
- Select Actions to confirm that the checkbox action follows the filtered list-item locator.
- Select Source to connect the selected action to its line in tests/todo.spec.ts.
- Select Console to inspect messages captured during the test.
- Select Network to inspect requests made while TodoMVC loaded.
You should see both tests marked as passed. The completion trace shows one checkbox action scoped through the todo containing Review pull request.
- Switch back to the PowerShell terminal from earlier.
- Press Ctrl+C to stop the local report server.
Report did not open?
- Check that the traced test run completed before starting the report server.
- Use the local address printed in PowerShell if the browser did not open automatically.
- Keep the report server running while loading that address.
- Help me open the Playwright HTML report.
Your local quality gate now passes with a unique locator plus inspectable diagnostics. Next, you will publish it through GitHub Actions so every proposed change runs the same checks.
Publish the Quality Gate
Your two-test Playwright suite already turns visible TodoMVC behavior into reliable evidence. It proves the checks pass on your Windows computer.
That evidence currently lives on one machine. A continuous integration quality gate uses GitHub Actions to repeat the same checks in a clean environment whenever code reaches the repository.
In this step, get ready to:
- Prepare the repository for automated testing.
- Commit the completed project with Git.
- Verify the hosted quality gate on GitHub.
Document the project and define the workflow
A README gives visitors a quick path from the project's purpose to a local test run. It turns the repository into evidence that another person can understand and reproduce.
- Create README.md inside C:\Projects\playwright-todo-quality-gate by adding the code below:
# Playwright Todo Quality Gate
A small end-to-end testing portfolio project built with Playwright Test and TypeScript. It verifies that a user can add and complete tasks in Playwright's TodoMVC demo, then runs the checks in GitHub Actions.
## What this demonstrates
- User-facing Playwright locators
- Strict locator diagnosis and filtering
- Auto-retrying web assertions
- Failure screenshots and retry traces
- HTML reports in GitHub Actions
## Run locally
```powershell
npm ci
npx playwright install chromium
npm test
```
Open the latest HTML report:
```powershell
npm run report
```
What does this README cover?
The Markdown page explains the user journey covered by the suite. It also highlights the locator design and diagnostics behind the quality gate.
The local commands use the committed lockfile before installing Chromium. The final script opens the latest HTML report.
- Save README.md.
- Confirm that README.md appears beside package.json in the VS Code Explorer sidebar.
You now have a repository landing page that explains the test suite and its local commands.
README preview looking wrong?
- Check that every heading begins with the same number of hash characters shown in the reference.
- Confirm that each PowerShell section has an opening fence and a closing fence.
- Ask for help with the file structure: Help me check the Markdown formatting in my Playwright README.
✔️ Awesome, I've got everything!
Your README.md now documents the project and the commands needed to reproduce it.
ⓧ I'd like to double check the full code
# Playwright Todo Quality Gate
A small end-to-end testing portfolio project built with Playwright Test and TypeScript. It verifies that a user can add and complete tasks in Playwright's TodoMVC demo, then runs the checks in GitHub Actions.
## What this demonstrates
- User-facing Playwright locators
- Strict locator diagnosis and filtering
- Auto-retrying web assertions
- Failure screenshots and retry traces
- HTML reports in GitHub Actions
## Run locally
```powershell
npm ci
npx playwright install chromium
npm test
```
Open the latest HTML report:
```powershell
npm run report
```
What should match?
Compare every heading and command with your saved file. Keep the two PowerShell code sections separate.
The automation instructions live in a YAML workflow. Its first version installs the locked dependencies before running the same tests that pass locally.
- Create a folder named .github inside C:\Projects\playwright-todo-quality-gate using the VS Code Explorer sidebar.
You should see .github appear in the Explorer sidebar.
- Create a folder named workflows inside .github using the Explorer sidebar.
You should now see the nested .github/workflows path.
- Create playwright.yml inside .github/workflows by adding the initial workflow below:
name: Playwright quality gate
on:
push:
branches: [main]
pull_request:
branches: [main]
permissions:
contents: read
jobs:
test:
timeout-minutes: 10
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 24.21.0
cache: npm
- name: Install dependencies
run: npm ci
- name: Install Chromium
run: npx playwright install --with-deps chromium
- name: Run Playwright tests
run: npm test
How does the quality gate work?
- The workflow responds to pushes on main.
- The pull request trigger checks proposed changes before they merge into main.
- The read-only permission lets the job access the repository contents.
- The Ubuntu job checks out the code before preparing Node.js 24.21.0 with npm caching.
- The remaining steps install the locked dependencies and Chromium before running the test script.
- Save .github/workflows/playwright.yml.
- Confirm that the Explorer sidebar shows playwright.yml inside .github/workflows.
The workflow can now run the two tests on a clean Ubuntu runner.
Workflow indentation looking uneven?
- Use spaces for every indentation level in playwright.yml.
- Check that each entry under steps begins at the same indentation level.
- Ask for help with the structure: Help me find the YAML indentation problem.
A passing run also needs evidence that survives after the job finishes. An artifact stores the generated HTML report for later inspection.
- Add the report upload step below the existing test step in .github/workflows/playwright.yml using this code:
- name: Upload HTML report
if: ${{ !cancelled() }}
uses: actions/upload-artifact@v7
with:
name: playwright-report
path: playwright-report/
retention-days: 7
Why upload the report?
The upload step preserves playwright-report/ after the test process ends. The retention-days setting keeps that evidence available for 7 days.
The condition still uploads the report when a test fails. A cancelled workflow skips the upload.
- Save .github/workflows/playwright.yml.
- Confirm that the workflow ends with an upload step containing playwright-report and a retention value of 7.
Your workflow now produces an inspectable report after every completed run.
Artifact step outside the job?
- Align the upload step with the other entries under steps.
- Keep the artifact settings nested under the upload step's with key.
- Ask for help with the final block: Help me place the report upload step correctly.
✔️ Awesome, I've got everything!
Your workflow now runs the Playwright suite and retains its HTML report.
ⓧ I'd like to double check the full code
name: Playwright quality gate
on:
push:
branches: [main]
pull_request:
branches: [main]
permissions:
contents: read
jobs:
test:
timeout-minutes: 10
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 24.21.0
cache: npm
- name: Install dependencies
run: npm ci
- name: Install Chromium
run: npx playwright install --with-deps chromium
- name: Run Playwright tests
run: npm test
- name: Upload HTML report
if: ${{ !cancelled() }}
uses: actions/upload-artifact@v7
with:
name: playwright-report
path: playwright-report/
retention-days: 7
What should match?
Compare the triggers and job settings with your saved workflow. Confirm that the upload step remains inside the test job.
Commit the completed project
A Git commit captures the exact files that make up the quality gate. The generated lockfile gives the remote runner the same dependency versions used locally.
- Create the local repository and its first commit by running these commands in the existing VS Code PowerShell terminal:
git init -b main
git add .
git commit -m "Build Playwright quality gate"
What do these commands do?
- The first command creates a repository whose initial branch is named main.
- The second command stages the project files while respecting .gitignore.
- The final command records the staged files as one project snapshot.
The terminal should confirm that a commit was created on main. Its file summary should include README.md and .github/workflows/playwright.yml.
Commit not created?
- Confirm that the terminal path is C:\Projects\playwright-todo-quality-gate.
- Check that your Git name and email remain configured from the setup step.
- Ask for help with the commit: Help me diagnose why my first Git commit failed.
Push the repository and verify the quality gate
A public GitHub repository gives the workflow a hosted place to react to pushes and pull requests. The first push triggers the quality gate automatically.
- Open the GitHub homepage in your browser.
- Sign in with the verified account from the setup step.
- Select New repository from the repository menu.
- Enter playwright-todo-quality-gate in the Repository name field.
- Select Public for the repository visibility.
- Leave the README and license initialization options unselected.
- Leave the .gitignore initialization option unselected.
- Click Create repository.
GitHub now shows Quick Setup instructions for the empty repository. Its remote URL connects your local commit to this hosted destination.
- Copy the remote URL shown under Quick Setup into REMOTE-URL.
The next push may open a browser authentication prompt. This connects Git to your verified GitHub account without placing credentials in the project.
- Connect the local repository and push main by running these commands:
git remote add origin [[REMOTE_URL="REMOTE-URL"]]
git push -u origin main
What does this publish?
The remote named origin points to your new GitHub repository. The push uploads the commit and connects the local main branch to its hosted counterpart.
- Complete GitHub authentication in the browser if prompted.
- Refresh the repository page after the push finishes.
You should see the committed project files on the repository page. The README should render below the file list.
Push rejected or authentication interrupted?
- Confirm that the remote URL belongs to the empty playwright-todo-quality-gate repository.
- Complete the browser authentication flow before returning to the terminal.
- Ask for help with the push: Help me diagnose my GitHub push failure.
Before you check the automation, do you think the clean runner will reproduce the two passing local tests?
- Select the Actions tab in the repository navigation.
- Select Playwright quality gate from the workflow list.
- Open the newest workflow run.
- Wait for the test job to finish.
The first run can take a few minutes while the clean runner installs the dependencies and Chromium. A running job during that preparation is expected.
You should see a successful run for Playwright quality gate. That result proves the two tests passed in a clean Linux environment.
- Open the successful run summary.
- Confirm that playwright-report appears in the artifacts area.
That is the full quality gate working. Your repository now reruns the browser checks automatically and preserves the HTML evidence for inspection.
Secret mission
Run the Suite Across Three Browser Engines
Your two TodoMVC tests already protect the Chromium experience. Extend the same shared suite across Chromium, Firefox, and WebKit locally and in CI without duplicating a test.
Clean Up Your Resources
Clean Up Your Resources
Your public GitHub repository uses standard GitHub-hosted runners at $0. Its combined HTML report artifact expires after 7 days.
Choose whether to keep the project available, pause its automation, or delete its resources.
Resources you used:
- Local project folder at C:\Projects\playwright-todo-quality-gate.
- Browser binaries installed through Playwright for Chromium, Firefox, plus WebKit.
- Public GitHub repository playwright-todo-quality-gate. It includes the GitHub Actions workflow plus its HTML report artifact.
Keep everything running
No action needed. Choose this if you plan to keep testing changes with the cross-browser quality gate.
- Leave the workflow enabled for future pushes plus pull requests.
- Keep C:\Projects\playwright-todo-quality-gate for future test changes.
- Keep the installed browser binaries so local cross-browser runs remain ready.
- Allow existing report artifacts to expire automatically after 7 days.
Pause - I'll come back to this later
Stop future workflow runs while keeping the repository plus local project ready for later.
- Return to the playwright-todo-quality-gate repository on GitHub.
- Select the Actions tab.
- Select Playwright quality gate from the workflow list.
- Open the workflow dropdown menu.
- Choose Disable workflow.
The workflow is now paused. Your repository plus local files remain available.
Delete - I don't want to use this again
Deleting the browser binaries, local folder, plus repository is permanent. Your pushed project already contains the final cross-browser work.
- Remove the locally installed browser binaries from the VS Code PowerShell terminal by running this command:
npx playwright uninstall
What does this command do?
Playwright removes the Chromium, Firefox, plus WebKit binaries associated with this installation. Your project files remain in place for the next cleanup action.
- Close VS Code so the project folder is no longer active.
- Press the Windows key to open search.
- Type PowerShell into the search box.
- Press Enter to open PowerShell.
- Delete the local project folder by running these commands:
Set-Location C:\Projects
Remove-Item -Recurse -Force .\playwright-todo-quality-gate
Get-ChildItem
How does this confirm deletion?
- The Set-Location command moves PowerShell outside the project folder before deletion.
- The Remove-Item command permanently removes the local project folder.
- The Get-ChildItem command lists the remaining contents of C:\Projects. You should no longer see playwright-todo-quality-gate.
Folder still listed?
Close any window or terminal still using the project folder before retrying the commands. Help me remove the local project folder safely.
- Return to the playwright-todo-quality-gate repository on GitHub.
- Select Settings.
- Select General.
- Scroll to Danger Zone.
- Select Delete this repository.
- Complete the repository deletion confirmations.
- Return to your GitHub repository list to confirm playwright-todo-quality-gate is absent.
The repository is now removed from your account. Its workflow plus stored report artifact are gone with it.
Nice Work!
Nice Work!
You did it! You built a Playwright Test quality gate that turns visible TodoMVC behavior into automated evidence.
You've learned how to:
- Build a TypeScript test suite that verifies visible add-todo and complete-todo journeys through user-facing locators.
- Diagnose a strict locator failure from its call log. Resolve the ambiguity with a role-based locator filtered by visible todo text. Investigate later failures with screenshots, retry traces, and HTML reports.
- Publish a GitHub Actions quality gate that checks every push or pull request. Preserve the combined report as a downloadable artifact.
- Secret Mission: Extend the shared suite across Chromium, Firefox, and WebKit without duplicating the test files.
Ready to quiz yourself?