Skip to content

Commit

Permalink
Add screenshot tests
Browse files Browse the repository at this point in the history
  • Loading branch information
sockmaster27 committed Oct 27, 2024
1 parent a19c7c5 commit b4b9de7
Show file tree
Hide file tree
Showing 39 changed files with 305 additions and 2 deletions.
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,9 @@ Thumbs.db
# Vite
vite.config.js.timestamp-*
vite.config.ts.timestamp-*

# Playwright
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
86 changes: 85 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@
"check:svader": "cd packages/svader && npm run check",
"check:tests-svelte4": "cd packages/tests-svelte4 && npm run check",
"build": "npm run package && npm run build:tests-svelte4",
"build:tests-svelte4": "cd packages/tests-svelte4 && npm run build"
"build:tests-svelte4": "cd packages/tests-svelte4 && npm run build",
"test": "playwright test"
},
"devDependencies": {
"@playwright/test": "^1.48.1",
"@types/node": "^22.7.9"
}
}
89 changes: 89 additions & 0 deletions playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { defineConfig, devices } from '@playwright/test';

// Apparently, Chromium browsers need to be explicitly told to use GPU acceleration in headless mode,
// and software rendering creates slightly different results for WebGL in noise and such,
// while WebGPU has no software fallback at all.
const chromiumHardware = {
args: [
"--enable-gpu",
],
};

/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: './tests',
snapshotPathTemplate: '{testDir}/.generated-screenshots/{testFilePath}/{arg}{ext}',
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',

contextOptions: {
// Disable animations so screenshots are comparable
reducedMotion: 'reduce',
},
},

projects: [
{
name: 'Firefox',
use: {
...devices['Desktop Firefox'],
},
},
{
name: 'Microsoft Edge',
use: {
...devices['Desktop Edge'],
channel: 'msedge',
launchOptions: chromiumHardware,
},
},
{
name: 'Google Chrome',
use: {
...devices['Desktop Chrome'],
channel: 'chrome',
launchOptions: chromiumHardware,
},
},
/* TODO: Test these as well */
// {
// name: 'Chromium',
// use: {
// ...devices['Desktop Chrome'],
// launchOptions: chromiumHardware,
// },
// },
// {
// name: 'WebKit',
// use: { ...devices['Desktop Safari'] },
// },
// {
// name: 'Mobile Chrome',
// use: { ...devices['Pixel 5'] },
// },
// {
// name: 'Mobile Safari',
// use: { ...devices['iPhone 12'] },
// },
],

webServer: {
command: 'npm run preview:v4',
port: 4173,
reuseExistingServer: !process.env.CI,
},
});
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
113 changes: 113 additions & 0 deletions tests/svelte4.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { test, expect, TestInfo, Page } from "@playwright/test";

// No output on the console is desired
test.beforeEach(async ({ page }) => {
page.on("console", (msg) => {
throw new Error(`Output printed to console:\n${msg.text()}`);
});
});

/** Project names (as defined in playwright.config.ts) that are not expected to support WebGPU. */
const webGpuUnsupported = [
"Firefox",
]

/**
* Takes a screenshot of the current page and compares it to a reference, as indexed by the given arguments.
* If a reference screenshot does not exist, it will be created and used for future assertions.
*
* @param page
* @param info
* @param name A name that identifies this test.
* @param api The API used in the test, either `"webgl"` or `"webgpu"`.
* @param number A number identifying the specific screeenshot within the test, if multiple are included.
*/
async function assertScreenshot(page: Page, info: TestInfo, name: string, api: "webgl" | "webgpu", number?: number) {
const projectName = info.project.name;
const isMobile = info.project.use.isMobile ?? false;

const mobileString = isMobile ? "-mobile" : "";
const numberString = number ? `-${number}` : "";

const isWebGpu = api === "webgpu";
const isWebGpuUnsupported = webGpuUnsupported.includes(projectName);
const unsupportedString = (isWebGpu && isWebGpuUnsupported) ? "-unsupported" : "";

const fileName = `${name}-${api}${unsupportedString}${mobileString}${numberString}.png`;

await expect.soft(page).toHaveScreenshot(fileName, {
// Allow for slightly different rendering on different platforms,
// since Chrome and Firefox have slightly different text-rendering and inputs etc.
maxDiffPixelRatio: 0.01,
});
}

const apis = ["webgl", "webgpu"] as const;
apis.forEach(api => {

test(`Hello world [${api}]`, async ({ page }, info) => {
const pageName = "hello-world";

await page.goto(`/${pageName}/${api}`);
await assertScreenshot(page, info, pageName, api);
});

test(`Remounting canvas [${api}]`, async ({ page }, info) => {
const pageName = "remount";

await page.goto(`/${pageName}/${api}`);
let show = page.getByLabel("Show")
await show.uncheck();
await assertScreenshot(page, info, pageName, api, 1);
for (let i = 0; i < 10; i++) {
await show.check();
await show.uncheck();
}
await show.check();
await assertScreenshot(page, info, pageName, api, 2);
});

test(`Oversized canvas [${api}]`, async ({ page }, info) => {
const pageName = "oversized-canvas";

await page.goto(`/${pageName}/${api}`);
await assertScreenshot(page, info, pageName, api, 1);
// Scroll to bottom-right corner
await page.evaluate(() => window.scrollBy(document.body.scrollWidth, document.body.scrollHeight));
await assertScreenshot(page, info, pageName, api, 2);
});

test(`Logo [${api}]`, async ({ page }, info) => {
const pageName = "logo";

await page.goto(`/${pageName}/${api}`);
await assertScreenshot(page, info, pageName, api);
});

test(`Landing page with bubbles [${api}]`, async ({ page }, info) => {
const pageName = "landing-page-bubbles";

await page.goto(`/${pageName}/${api}`);
await assertScreenshot(page, info, pageName, api);
});

test(`Landing page with a halo [${api}]`, async ({ page }, info) => {
const pageName = "landing-page-halo";

await page.goto(`/${pageName}/${api}`);
await assertScreenshot(page, info, pageName, api);
});

test(`Slider component [${api}]`, async ({ page }, info) => {
const pageName = "slider";

await page.goto(`/${pageName}/${api}`);
const slider = page.getByRole("slider");
await assertScreenshot(page, info, pageName, api, 1);
slider.fill("1");
await assertScreenshot(page, info, pageName, api, 2);
slider.fill("0");
await assertScreenshot(page, info, pageName, api, 3);
});

});
6 changes: 6 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"compilerOptions": {
"types": ["node"],
"strict": true,
}
}

0 comments on commit b4b9de7

Please sign in to comment.