QA Career
Deploy a Test Report Portfolio on GitHub Pages
Learn to deploy test report portfolio GitHub Pages sites with Playwright reports, safe CI workflows, custom styling, history, and recruiter-ready proof.
17 min read | 2,575 words
TL;DR
Create a small Playwright project, generate its HTML report, combine that report with a recruiter-friendly landing page, and deploy the combined site through GitHub Actions using the official Pages actions. Sanitize all evidence, preserve the workflow result, and verify the public URL before adding it to your resume.
Key Takeaways
- Use GitHub Actions artifacts and the official Pages deployment actions instead of committing generated reports.
- Publish a curated portfolio landing page that links to a sanitized Playwright HTML report.
- Configure Playwright so reports work correctly below a GitHub Pages repository path.
- Run portfolio-safe tests against a public demo application and never publish secrets or private evidence.
- Keep failed-test deployment intentional so recruiters can inspect both the report and the workflow result.
- Add project context, test scope, limitations, and a clear source-code link to make the report meaningful.
- Verify links, mobile layout, workflow permissions, and deployed assets before sharing the portfolio.
To deploy test report portfolio GitHub Pages content professionally, publish more than a raw test runner screen. Build a small public site that explains the project, links to source code, and opens a real, sanitized HTML report generated by your automation suite.
This tutorial takes you from an empty repository to a public portfolio URL using Playwright, GitHub Actions, and the official GitHub Pages deployment workflow. For the broader strategy behind choosing projects and presenting evidence, read the QA Portfolio Job Search Complete Guide.
You will use a public demo application, so the finished site is safe to share with recruiters. The same deployment pattern works for other static reporters, but Playwright provides a complete example that you can run and verify.
What You Will Build
You will create a repository that contains:
- A TypeScript Playwright test suite for a public demo store.
- A generated HTML test report with traces and screenshots on failure.
- A responsive portfolio landing page that explains your test strategy and evidence.
- A GitHub Actions workflow that runs tests and deploys static files to GitHub Pages.
- A public URL shaped like
https://YOUR-USER.github.io/qa-report-portfolio/.
The landing page is the recruiter entry point. The report is supporting evidence, not the whole story. That distinction turns routine CI output into a portfolio artifact.
Prerequisites
Install Node.js 22 LTS or a newer supported LTS release, Git 2.39 or newer, and a current desktop browser. You also need a GitHub account and permission to create a public repository. GitHub Pages availability can vary for private repositories and account plans, so a public practice repository is the simplest option.
Confirm your local tools:
node --version
npm --version
git --version
Use Node 22 for this tutorial. Commands assume a POSIX-like terminal, but the npm commands also work in PowerShell. Do not begin with production tests or employer-owned reports. Use only data, screenshots, URLs, and code you are authorized to publish.
Here is the deployment approach you will use:
| Approach | Generated files in Git history | Preserves workflow evidence | Recommended use |
|---|---|---|---|
Commit report to docs/ |
Yes | Partly | Tiny manual demos |
| Push generated site to a branch | Usually | Yes | Legacy Pages setups |
| Official Pages artifact workflow | No | Yes | This portfolio tutorial |
The artifact workflow keeps generated HTML out of your source commits. It also gives reviewers a visible Actions run tied to each deployment.
Step 1: Create the Playwright Portfolio Project
Create the project, install Playwright Test, and install Chromium:
mkdir qa-report-portfolio
cd qa-report-portfolio
npm init -y
npm install --save-dev @playwright/test typescript
npx playwright install chromium
mkdir -p tests portfolio
Add these scripts and metadata to package.json:
{
"name": "qa-report-portfolio",
"version": "1.0.0",
"private": true,
"description": "Public QA portfolio with Playwright test evidence",
"scripts": {
"test": "playwright test",
"test:report": "playwright test && playwright show-report"
},
"devDependencies": {
"@playwright/test": "^1.55.0",
"typescript": "^5.8.0"
}
}
The version ranges are examples of a 2026-compatible baseline. Your lockfile records the exact resolved versions, which makes CI reproducible. Commit package-lock.json, and let Dependabot or deliberate maintenance handle updates.
Create .gitignore:
node_modules/
test-results/
playwright-report/
site/
.env
Generated reports are ignored because the workflow will package and deploy them. The .env rule is a final guard, not permission to put real credentials in portfolio tests.
Verification: Run npm test -- --list. Playwright should start successfully and report zero tests rather than a missing command or package error. Run git status --short and confirm that node_modules is not listed.
Step 2: Configure a Portable HTML Report
Create playwright.config.ts at the repository root:
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
fullyParallel: true,
forbidOnly: Boolean(process.env.CI),
retries: process.env.CI ? 1 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: [
['list'],
['html', { outputFolder: 'playwright-report', open: 'never' }],
],
use: {
baseURL: 'https://www.saucedemo.com',
trace: 'retain-on-failure',
screenshot: 'only-on-failure',
video: 'off',
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
],
});
Playwright's HTML report uses relative asset links, so the report remains portable when copied into site/report/. open: 'never' prevents CI from trying to launch a browser. A single CI worker makes this small public demonstration easier to read and reduces variability. In a production suite, choose worker count based on isolation and runtime. If the runner and locator concepts are new to you, complete the getting started with Playwright guide before expanding this project.
Traces can contain page content, network details, and entered values. They are useful evidence only when the target and data are public. Keep video off unless it adds clear value because videos increase artifact size and review time.
Verification: Run npx playwright test --list. The output should include a chromium project and point to the tests directory. No browser window should open.
Step 3: Add Recruiter-Readable Tests
Create tests/checkout.spec.ts:
import { expect, test } from '@playwright/test';
test.describe('Sauce Demo purchase journey', () => {
test('standard user can add a backpack to the cart', async ({ page }) => {
await test.step('Sign in with the public demo account', async () => {
await page.goto('/');
await page.getByPlaceholder('Username').fill('standard_user');
await page.getByPlaceholder('Password').fill('secret_sauce');
await page.getByRole('button', { name: 'Login' }).click();
await expect(page).toHaveURL(/inventory/);
});
await test.step('Add the backpack and verify the cart', async () => {
const product = page.getByText('Sauce Labs Backpack', { exact: true });
await expect(product).toBeVisible();
await page.getByRole('button', { name: 'Add to cart' }).first().click();
await page.getByTestId('shopping-cart-link').click();
await expect(page.getByText('Sauce Labs Backpack', { exact: true })).toBeVisible();
await expect(page.getByText('$29.99', { exact: true })).toBeVisible();
});
});
test('locked user receives an actionable error', async ({ page }) => {
await page.goto('/');
await page.getByPlaceholder('Username').fill('locked_out_user');
await page.getByPlaceholder('Password').fill('secret_sauce');
await page.getByRole('button', { name: 'Login' }).click();
await expect(page.getByTestId('error')).toContainText(
'Sorry, this user has been locked out.'
);
});
});
Named test.step blocks make the HTML report understandable to someone who did not write the code. The tests use accessible locators and visible business assertions instead of implementation-heavy selectors. One happy path and one negative path also demonstrate that your scope is risk-based, not just a collection of clicks.
The credentials belong to the public demo application. Never treat this example as a reason to hard-code credentials for a real system. For nonpublic environments, inject secrets in CI and prevent reports from exposing them.
Verification: Run npm test. Expect two passing tests and a playwright-report/index.html file. Open it with npx playwright show-report, expand the purchase test, and confirm the named steps are visible. Stop the report server with Ctrl+C.
Step 4: Build the Portfolio Landing Page
Create portfolio/index.html:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="QA automation portfolio with Playwright test evidence.">
<title>QA Test Report Portfolio</title>
<style>
:root { color-scheme: dark; font-family: Inter, system-ui, sans-serif; }
* { box-sizing: border-box; }
body { margin: 0; background: #07111f; color: #e7eef8; line-height: 1.6; }
main { width: min(960px, 90%); margin: auto; padding: 64px 0; }
.eyebrow { color: #67e8f9; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; }
h1 { max-width: 760px; font-size: clamp(2.3rem, 7vw, 4.8rem); line-height: 1.05; margin: 12px 0 20px; }
.lead { max-width: 720px; color: #b8c7dc; font-size: 1.15rem; }
.actions { display: flex; flex-wrap: wrap; gap: 12px; margin: 32px 0 48px; }
a.button { padding: 12px 18px; border-radius: 10px; background: #22d3ee; color: #06202a; font-weight: 800; text-decoration: none; }
a.secondary { background: transparent; color: #e7eef8; border: 1px solid #52647a; }
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 16px; }
article { padding: 22px; border: 1px solid #26364a; border-radius: 14px; background: #0d1a2b; }
h2 { margin-top: 52px; }
h3 { margin-top: 0; }
code { color: #67e8f9; }
</style>
</head>
<body>
<main>
<p class="eyebrow">QA Automation Case Study</p>
<h1>Readable tests. Traceable evidence. Reproducible CI.</h1>
<p class="lead">I tested a public commerce demo with Playwright and TypeScript. This portfolio shows the scope, decisions, source, and latest CI-generated report.</p>
<div class="actions">
<a class="button" href="./report/">Open test report</a>
<a class="button secondary" href="https://github.com/YOUR-USER/qa-report-portfolio">View source</a>
</div>
<section class="grid" aria-label="Project summary">
<article><h3>Scope</h3><p>Authentication, inventory, cart behavior, and negative access control.</p></article>
<article><h3>Stack</h3><p>Playwright Test, TypeScript, Chromium, and GitHub Actions.</p></article>
<article><h3>Evidence</h3><p>Assertions, named test steps, failure screenshots, traces, and workflow logs.</p></article>
</section>
<h2>Test approach</h2>
<p>The suite prioritizes one revenue path and one high-signal negative scenario. Tests use role, placeholder, and test-id locators, with assertions at business-relevant checkpoints.</p>
<h2>Known limitations</h2>
<p>This demonstration runs on Chromium with one CI worker. It does not claim full browser, accessibility, performance, or production coverage.</p>
</main>
</body>
</html>
Replace YOUR-USER with your GitHub username in both the source URL and later commands. Keep limitations visible. Honest boundaries signal stronger judgment than inflated coverage claims. You can add a concise architecture diagram later, but the first version should favor evidence and fast navigation.
Verification: From the project root, run npx http-server portfolio -p 4173 if you already have an HTTP server, or npx serve portfolio. Open the printed local URL. Confirm that the layout reflows on a narrow viewport. The report link will return 404 until the next step, which is expected.
Step 5: Assemble and Check the Static Site
Create scripts/build-site.mjs after making the scripts directory:
mkdir -p scripts
import { cp, mkdir, rm, stat } from 'node:fs/promises';
await rm('site', { recursive: true, force: true });
await mkdir('site', { recursive: true });
await cp('portfolio', 'site', { recursive: true });
await cp('playwright-report', 'site/report', { recursive: true });
await stat('site/index.html');
await stat('site/report/index.html');
console.log('Static portfolio assembled in site/');
Add a build command to the scripts object in package.json:
{
"scripts": {
"test": "playwright test",
"test:report": "playwright test && playwright show-report",
"build:site": "node scripts/build-site.mjs"
}
}
The Node script fails if either required entry page is absent. This catches silent deployments where the landing page exists but the report was never copied. It uses only built-in Node APIs, so you do not need another build dependency.
Run the complete local flow:
npm test
npm run build:site
npx serve site
Verification: Open the local site and select Open test report. The browser should load report/index.html, and report styles and icons should render. Return to the landing page and confirm that the source link points to your intended repository, even if that repository does not exist yet.
Step 6: Deploy Test Report Portfolio GitHub Pages with Actions
Create .github/workflows/pages.yml:
name: Test and deploy portfolio
on:
push:
branches: [main]
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: pages
cancel-in-progress: true
jobs:
test-and-build:
runs-on: ubuntu-latest
steps:
- name: Check out source
uses: actions/checkout@v4
- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- name: Install dependencies
run: npm ci
- name: Install Chromium
run: npx playwright install --with-deps chromium
- name: Run portfolio tests
run: npm test
- name: Assemble static site
run: npm run build:site
- name: Configure Pages
uses: actions/configure-pages@v5
- name: Upload Pages artifact
uses: actions/upload-pages-artifact@v3
with:
path: site
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: test-and-build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
This workflow deploys only after tests pass. That is the cleanest default for a portfolio because the public report represents a verified revision. The permissions are intentionally narrow: read repository content, write Pages, and request the identity token used by Pages deployment. For more context on pipeline design and quality gates, use the CI/CD for QA beginners guide.
If your learning goal is to display a failing report, do not append || true to the test command because that hides the result. A better advanced design captures the test exit code, builds and uploads the report with if: always(), then ends the job with the original failure. Keep the main public site on the latest passing report and share failed-run artifacts through Actions when relevant.
Verification: Validate the YAML indentation, then commit it. On GitHub, open the Actions tab after pushing. The test-and-build job should pass before the deploy job starts, and the deployment environment should show a Pages URL.
Step 7: Publish the Repository and Enable Pages
Initialize Git locally if needed, create the GitHub repository, and push:
git init
git add .
git commit -m "Build QA test report portfolio"
git branch -M main
git remote add origin https://github.com/YOUR-USER/qa-report-portfolio.git
git push -u origin main
You may instead create the remote with GitHub CLI using gh repo create qa-report-portfolio --public --source=. --remote=origin --push. Do not run both remote-creation approaches.
In the repository, open Settings, then Pages. Under Build and deployment, choose GitHub Actions as the source if GitHub has not selected it automatically. Open the workflow run and inspect every command. A green deployment without green tests is not the outcome you want.
Your final URL will normally be https://YOUR-USER.github.io/qa-report-portfolio/. GitHub may take a short time to serve a new deployment. Repository Pages sites live below a path, which is why all landing-page links in this tutorial use relative URLs such as ./report/. Root-relative links such as /report/ would incorrectly point at the user site's domain root.
Verification: Open the Pages URL in a private browser window. Load the landing page, open the report, navigate into both tests, and refresh a nested report page. Check the browser console for missing assets. Finally, inspect the latest Actions run and confirm its deployment URL matches the page you reviewed.
Step 8: Make the Portfolio Recruiter-Ready
A deployed report proves that tooling ran. It does not explain why the work matters. Add a strong README.md with the project goal, risk model, tested and untested scope, local commands, CI design, report URL, and a short note about the public demo data. The guide to writing a QA GitHub project README for recruiters gives you a focused structure.
Then improve the evidence trail:
- Pin the repository on your GitHub profile.
- Put the live report link near the top of the README and repository About section.
- Add concise commit messages that show the project evolving.
- Create one issue describing a test risk or future enhancement.
- Link the portfolio from your resume with a label such as
Playwright QA case study. - Recheck the public report after dependency updates.
For deeper reasoning, turn this project into a written QA portfolio test strategy case study. If you want a faster human introduction, create a test automation portfolio demo video that opens the landing page, runs one test, and explains one design choice.
Verification: Ask someone unfamiliar with the repository to spend 60 seconds on the landing page and README. They should be able to state what you tested, why you chose the scenarios, where the code lives, whether CI passed, and what the limitations are. If not, revise the first screen and README summary.
Troubleshooting Deploy Test Report Portfolio GitHub Pages Workflows
Problem: The Pages URL returns 404 -> Confirm the workflow completed its deploy job, Pages uses GitHub Actions as its source, and the repository is public or eligible for Pages under your plan. Verify that site/index.html exists before upload.
Problem: The landing page works but the report link returns 404 -> Run npm run build:site and confirm site/report/index.html exists. Use href="./report/", not /report/, because a project site is hosted below the repository path.
Problem: The HTML report loads without styles or attachments -> Do not copy only playwright-report/index.html. Copy the entire report directory because its data and assets are separate files. Test the assembled site directory through an HTTP server before pushing.
Problem: npm ci fails in Actions -> Commit the package-lock.json produced by npm and keep it synchronized with package.json. If you changed dependencies manually, run npm install locally, inspect the lockfile diff, and commit it.
Problem: Tests pass locally but fail in CI -> Read the trace and screenshot from the workflow artifact or report. Check timing, test isolation, browser installation, data dependencies, and viewport assumptions. Do not add arbitrary waits. Prefer web-first assertions such as await expect(locator).toBeVisible().
Problem: Deployment fails with a permissions or environment error -> Check the workflow-level pages: write and id-token: write permissions. Confirm that the deploy job uses the github-pages environment and that repository environment protection rules are not waiting for an unavailable reviewer.
Interview Questions and Answers
Q: Why use GitHub Pages for a test report portfolio?
GitHub Pages hosts static HTML close to the source and workflow history. A recruiter can move from project context to tests, CI evidence, and the rendered report without installing anything. It is a presentation layer, not a test management system.
Q: Why upload a Pages artifact instead of committing the report?
Generated reports create noisy diffs and expand repository history. The Pages artifact workflow keeps source commits focused while retaining a deployment record for each run. It also uses GitHub's supported Pages deployment actions.
Q: How do you prevent sensitive test data from entering a public report?
Use a public demo target and synthetic accounts. Review traces, screenshots, logs, URLs, and attachments as if they were public source code. Keep private environments out of the portfolio pipeline and never rely on masking alone.
Q: Should a failed test report be deployed?
The main portfolio URL should usually show the latest passing revision. Preserve failed reports as workflow artifacts and keep the job status failed so evidence is honest. A separate, intentionally labeled failure example can demonstrate debugging skills.
Q: How does this workflow handle a repository subpath?
The portfolio uses relative links, and Playwright's report assets are portable within their directory. This allows the whole site to work under /qa-report-portfolio/. Root-relative custom links would escape that repository path and should be avoided.
Q: What makes an automation report recruiter-friendly?
Context makes it recruiter-friendly. Explain the product risk, scope, scenario selection, assertions, CI behavior, and limitations before linking the raw report. Named test steps and readable test titles help a reviewer follow the evidence quickly.
Common Mistakes and Best Practices
Avoid publishing a report copied from work, even if you remove the company name. Traces and screenshots can retain customer data, internal URLs, tokens, headers, and application structure. Build the portfolio against an explicitly public demo instead.
Do not present a huge test count as the main achievement. A small suite with clear risk coverage, deterministic assertions, and an honest CI trail is easier to defend in an interview. Explain why each test exists.
Keep these practices in your maintenance routine:
- Pin exact dependency resolutions through the lockfile and review automated updates.
- Use official, versioned GitHub Actions and review major-version upgrades before merging.
- Give workflows only the permissions they need.
- Test relative links locally from the assembled output directory.
- Add a visible generation date only if it is generated automatically and accurately.
- Keep accessibility, mobile layout, and keyboard navigation usable on the landing page.
- Treat every trace, screenshot, video, and log line as public information.
- Re-run the project monthly while actively job searching so links and demo selectors do not decay.
Where To Go Next
Return to the complete QA portfolio job search guide and decide which artifact closes the biggest proof gap in your profile. Strengthen the repository narrative with the recruiter-focused QA GitHub README tutorial, document your decisions in a test strategy portfolio case study, or show your communication skills with a short automation demo video.
Choose one improvement, ship it, and link it from the same landing page. A connected body of evidence is more persuasive than several unrelated repositories.
Conclusion
To deploy test report portfolio GitHub Pages content that supports a job search, combine reproducible automation with clear project context. Run safe public tests in CI, assemble a static landing page and complete HTML report, deploy through the official Pages artifact workflow, and verify the public result as a recruiter would.
Your next step is simple: create the repository, replace the placeholder username, run the two tests locally, and push the workflow. Once the URL works in a private window, add the strategy and README context that lets your technical evidence tell a credible story.
Interview Questions and Answers
How would you deploy an automated test report to GitHub Pages?
I would run the tests in GitHub Actions, generate a static HTML report, and combine it with a small portfolio landing page. I would upload that directory with the official Pages artifact action and deploy it in a job using the github-pages environment. I would keep generated files out of source history and verify the public URL after deployment.
Why should a portfolio workflow fail when tests fail?
The workflow result is part of the evidence, so hiding a failure would make the signal misleading. I preserve failed reports with an always-run artifact upload while returning the original nonzero result. The primary public deployment remains tied to a passing revision.
What security risks exist when publishing test reports?
Reports can expose credentials, personal data, internal URLs, request details, screenshots, and trace content. I use synthetic data and public targets, minimize attachments, and review the generated output before publishing. I never repurpose employer reports for a personal portfolio without explicit authorization.
How do you make a static report work under a GitHub Pages project path?
I use relative links for custom navigation and keep the report's complete asset directory intact. I assemble the exact deployable directory locally and serve it over HTTP before pushing. That reproduces path behavior more accurately than opening index.html directly from disk.
Why use named test steps in a portfolio suite?
Named steps translate implementation into business actions that a reviewer can scan in the report. They also organize traces and make failures easier to locate. I keep steps meaningful and avoid wrapping every individual click.
What would you include on the landing page for a QA portfolio?
I would state the product area, tested risks, stack, evidence types, source link, and known limitations. The primary action would open the latest passing report. I would keep the page concise so a recruiter understands the project within about a minute.
How would you maintain a GitHub Pages test portfolio?
I would commit the lockfile, review dependency updates, rerun the suite regularly, and monitor links and public demo selectors. I would keep Actions on reviewed major versions and maintain least-privilege workflow permissions. During a job search, I would check the public site in a signed-out browser after material changes.
Frequently Asked Questions
Can I host a Playwright HTML report on GitHub Pages?
Yes. Playwright's HTML report is static and can be uploaded as part of a GitHub Pages artifact. Copy the complete report directory, not only index.html, so its assets and test data remain available.
Should I commit Playwright reports to my repository?
Usually no. Generated reports create noisy commits and can make repository history large. Build the report in GitHub Actions and deploy it as a Pages artifact while keeping the source and lockfile in Git.
Is GitHub Pages free for a public QA portfolio?
GitHub Pages is commonly available for public repositories, but current limits and private-repository eligibility depend on GitHub's plans and policies. Check your repository's Pages settings before choosing an access model.
How do I share failed test evidence without showing a broken portfolio?
Keep the main Pages deployment tied to passing tests. Upload failed reports as workflow artifacts, preserve the failed job status, and link a deliberately documented failure investigation only when it adds value.
What should a QA report portfolio include besides test results?
Include the tested risk, scope, stack, scenario rationale, source link, CI workflow, limitations, and instructions to reproduce the run. This context lets a reviewer judge your decisions rather than only count tests.
Can I publish reports from a real employer project?
Do not publish employer or client evidence without explicit authorization. Reports, screenshots, traces, and logs may contain private data even after obvious names are removed. Recreate the technique against a public demo application instead.
Why do GitHub Pages report links work locally but fail after deployment?
Repository Pages sites are hosted below a repository path. Root-relative links can point to the wrong location, so use relative links and test the complete assembled site through a local HTTP server before deployment.