Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 109 additions & 0 deletions .github/workflows/regression.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
name: Regression Tests

on:
# Trigger via PR comment: "/regression"
issue_comment:
types: [created]
# Also run on push to master for baseline updates
push:
branches: [master]
# Allow manual re-runs (useful for baseline setup)
workflow_dispatch:

# Explicit minimal permissions (fix: workflow missing permissions)
permissions:
contents: read
issues: write
pull-requests: write

jobs:
regression:
runs-on: ubuntu-latest
timeout-minutes: 15
# For issue_comment: only run on PR comments matching "/regression"
# For push: always run on master
# For workflow_dispatch: always run
# NOTE: issue_comment trigger only works when this workflow file exists on the default branch (master).
# You must merge this file to master first before /regression comments will trigger runs.
if: >-
github.event_name == 'push' ||
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'issue_comment' &&
github.event.issue.pull_request &&
contains(github.event.comment.body, '/regression'))
steps:
# Permission check for comment-triggered runs
- name: Check commenter permissions
if: github.event_name == 'issue_comment'
uses: actions/github-script@v4
id: check-access
with:
script: |
const { owner, repo } = context.repo;
const { login } = context.payload.comment.user;
const { data } = await github.repos.getCollaboratorPermissionLevel({ owner, repo, username: login });
if (data.permission !== 'write' && data.permission !== 'admin') {
core.setFailed(`User ${login} does not have write access`);
}
result-encoding: string

# Resolve immutable PR head SHA for comment-triggered runs
# (fix: TOCTOU — use head_sha not head_ref to prevent code changes between permission check and execution)
- name: Get PR head SHA
if: github.event_name == 'issue_comment'
uses: actions/github-script@v4
id: pr-sha
with:
script: |
const { owner, repo } = context.repo;
const { data: pr } = await github.pulls.get({
owner,
repo,
pull_number: context.issue.number
});
core.setOutput('sha', pr.head.sha);
result-encoding: string

- uses: actions/checkout@v5
with:
# Use immutable PR commit SHA for comment triggers (prevents TOCTOU),
# default ref for push/dispatch
ref: ${{ steps.pr-sha.outputs.sha || github.sha }}

- uses: actions/setup-node@v4
with:
node-version: 18
cache: yarn

- uses: actions/cache@v3
Comment thread Fixed
with:
path: |
packages/core/.local-chromium
key: chromium-${{ runner.os }}-${{ hashFiles('packages/core/package.json') }}

- run: yarn install --frozen-lockfile
- run: yarn build
Comment thread Fixed

- name: Install browser dependencies
Comment thread Fixed
run: sudo apt-get install -y libgbm-dev

- name: Run regression tests
run: yarn test:regression
env:
PERCY_TOKEN: ${{ secrets.PERCY_REGRESSION_TOKEN }}

# Post result back to PR for comment-triggered runs
- name: Comment result on PR
Comment thread Fixed
if: github.event_name == 'issue_comment' && always()
uses: actions/github-script@v4
with:
script: |
const status = '${{ job.status }}';
const emoji = status === 'success' ? '✅' : '❌';
const body = `${emoji} **Regression tests ${status}**\n\n[View run](${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId})`;
github.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body
});
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ oclif.manifest.json
packages/logger/test/client.js
packages/sdk-utils/test/client.js
packs
docs/
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
"test:coverage": "lerna run --stream --concurrency=1 --no-bail test:coverage",
"test:types": "lerna run --parallel test:types",
"global:link": "lerna exec -- yarn link",
"global:unlink": "lerna exec -- yarn unlink"
"global:unlink": "lerna exec -- yarn unlink",
"test:regression": "node test/regression/regression.test.js"
},
"devDependencies": {
"@babel/cli": "^7.11.6",
Expand Down
17 changes: 17 additions & 0 deletions test/regression/.percy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
version: 2
snapshot:
widths: [1280]
minHeight: 1024
percyCSS: |
*, *::before, *::after {
animation-duration: 0s !important;
transition-duration: 0s !important;
}
* { caret-color: transparent !important; cursor: none !important; }
discovery:
allowedHostnames:
- localhost:9101
networkIdleTimeout: 150
concurrency: 5
launchOptions:
timeout: 120000
47 changes: 47 additions & 0 deletions test/regression/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Percy CLI E2E Regression Tests

Visual regression tests that upload real snapshots to Percy, covering all asset discovery features.

## Setup

1. Create a Percy project at [percy.io](https://percy.io) named `percy-cli-regression`
2. Link it to the GitHub repo for VCS integration
3. Add `PERCY_REGRESSION_TOKEN` as a GitHub repository secret
4. Set the base branch to `master` in Percy project settings

## Running Locally

```bash
# Requires PERCY_TOKEN — skips gracefully without it
PERCY_TOKEN=your_token_here yarn test:regression
```

## How It Works

1. Starts two local servers: main (port 9100) and CORS (port 9101)
2. Runs `percy snapshot` against all pages defined in `snapshots.yml`
3. Percy uploads snapshots and creates a build
4. Visual diffs are reviewed in the Percy dashboard
5. Percy's GitHub VCS integration gates PRs via checks

## Adding a New Test

1. Create a new HTML page in `pages/`
2. Add an entry to `snapshots.yml`
3. (Optional) Add assets to `assets/`
4. (Optional) Add special server routes to `server.js`

No changes to `regression.test.js` needed.

## Configuration

- `snapshots.yml` — Snapshot definitions (URLs, names, per-snapshot options)
- `.percy.yml` — Percy project config (discovery settings, anti-flakiness CSS)

Each snapshot entry supports all Percy options: `widths`, `enableJavaScript`, `discovery.allowedHostnames`, `waitForSelector`, `execute`, `percyCSS`, etc.

## CI

Runs automatically on PRs and pushes to master via `.github/workflows/regression.yml` (Linux only).

**Important:** Never hardcode `PERCY_TOKEN` in any committed file. Always use environment variables.
47 changes: 47 additions & 0 deletions test/regression/assets/css/base.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/* Base styles for regression test pages */
body {
font-family: sans-serif;
margin: 0;
padding: 20px;
background: #fff;
color: #333;
}

h1 {
font-size: 24px;
margin-bottom: 16px;
}

h2 {
font-size: 18px;
margin-bottom: 12px;
color: #555;
}

.section {
margin-bottom: 24px;
padding: 16px;
border: 1px solid #ddd;
border-radius: 4px;
}

.section-title {
font-weight: bold;
margin-bottom: 8px;
}

img {
max-width: 100%;
height: auto;
}

/* Pseudo-class rules (tested in comprehensive.html) */
.hover-target:hover {
background-color: #e0e0ff;
border-color: #3333ff;
}

.focus-target:focus {
outline: 3px solid #3333ff;
box-shadow: 0 0 5px rgba(51, 51, 255, 0.5);
}
48 changes: 48 additions & 0 deletions test/regression/assets/css/responsive.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/* Responsive styles — visually distinct at each breakpoint */
.responsive-container {
padding: 20px;
text-align: center;
}

.responsive-grid {
display: grid;
gap: 16px;
grid-template-columns: repeat(3, 1fr);
}

.responsive-card {
padding: 20px;
background: #f0f4ff;
border: 2px solid #3366cc;
border-radius: 8px;
}

.breakpoint-indicator {
font-size: 24px;
font-weight: bold;
padding: 12px;
text-align: center;
border-radius: 4px;
}

/* Desktop: 3 columns, blue indicator */
.breakpoint-indicator { background: #ddeeff; color: #003366; }
.breakpoint-indicator::after { content: "Desktop (1280px)"; }

/* Tablet: 2 columns, green indicator */
@media (max-width: 768px) {
.responsive-grid {
grid-template-columns: repeat(2, 1fr);
}
.breakpoint-indicator { background: #ddf5dd; color: #003300; }
.breakpoint-indicator::after { content: "Tablet (768px)"; }
}

/* Mobile: 1 column, orange indicator */
@media (max-width: 375px) {
.responsive-grid {
grid-template-columns: 1fr;
}
.breakpoint-indicator { background: #ffedcc; color: #663300; }
.breakpoint-indicator::after { content: "Mobile (375px)"; }
}
Binary file added test/regression/assets/fonts/test-font.woff
Binary file not shown.
Binary file added test/regression/assets/fonts/test-font.woff2
Binary file not shown.
1 change: 1 addition & 0 deletions test/regression/assets/images/icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/regression/assets/images/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/regression/assets/images/photo.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/regression/assets/images/picture-lg.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/regression/assets/images/picture-sm.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/regression/assets/images/poster.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
77 changes: 77 additions & 0 deletions test/regression/assets/js/custom-element.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/* eslint-env browser */
// Custom element for regression testing
// Tests: shadow DOM, scoped styles, attributeChangedCallback, slotted content

class PercyTestCard extends HTMLElement {
static get observedAttributes() {
return ['title', 'theme'];
}

constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
shadow.innerHTML = `
<style>
:host {
display: block;
border: 2px solid #3366cc;
border-radius: 8px;
padding: 16px;
margin: 8px 0;
}
:host([theme="dark"]) {
background: #1a1a2e;
color: #eee;
border-color: #6699ff;
}
.card-title {
font-size: 16px;
font-weight: bold;
margin-bottom: 8px;
color: inherit;
}
.card-content {
font-size: 14px;
}
</style>
<div class="card-title"></div>
<div class="card-content">
<slot></slot>
</div>
`;
}

attributeChangedCallback(name, oldValue, newValue) {
if (name === 'title') {
const titleEl = this.shadowRoot.querySelector('.card-title');
if (titleEl) titleEl.textContent = newValue;
}
}

connectedCallback() {
const title = this.getAttribute('title');
if (title) {
this.shadowRoot.querySelector('.card-title').textContent = title;
}
}
}

class PercyNestedShadow extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
shadow.innerHTML = `
<style>
:host { display: block; padding: 8px; background: #f5f5f5; border-radius: 4px; }
p { color: #666; margin: 4px 0; }
</style>
<p>Nested shadow root content</p>
<percy-test-card title="Nested Card">
<span>Content inside nested shadow DOM</span>
</percy-test-card>
`;
}
}

customElements.define('percy-test-card', PercyTestCard);
customElements.define('percy-nested-shadow', PercyNestedShadow);
Loading
Loading