Tucker McKnight <tmcknight@instructure.com> | Sat Sep 05 2026
Add Playwright for browser tests, with some tests for the index page
2 3
example_site/node_modules
example_site/_site
docs2 3 4 5 6 7 8 9 10 11 12
example_site/node_modules
example_site/_site
docs
browser_tests/setup/node_modules
browser_tests/setup/_site
# Playwright
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
/playwright/.auth/
-1 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
import repoViewer, {beforeHook} from "eleventy-plugin-repoviewer"
import eleventyRss from "@11ty/eleventy-plugin-rss"
import { RenderPlugin } from "@11ty/eleventy"
import syntaxHighlight from "@11ty/eleventy-plugin-syntaxhighlight"
export default async function(eleventyConfig) {
eleventyConfig.addPlugin(eleventyRss)
eleventyConfig.addPlugin(RenderPlugin)
eleventyConfig.addPlugin(syntaxHighlight)
const reposConfig = {
baseUrl: "https://localhost:8080",
path: '/repos',
repos: {
"git static site UI": {
defaultTemplate: {
homepageButtons: [
{
url: "https://example.com",
text: "Setup Guide",
}
]
},
defaultBranch: "main",
tags: [],
branches: [
"**",
],
location: "../../",
description: "Do you like plain old HTML pages? Want to host git repos on your own site? Cool, me too.",
},
}
}
eleventyConfig.on("eleventy.beforeConfig", beforeHook(eleventyConfig, reposConfig))
eleventyConfig.on("eleventy.beforeConfig", async () => {
eleventyConfig.addPassthroughCopy("test.txt")
})
eleventyConfig.addPlugin(repoViewer, reposConfig)
};
-1 0 1 2 3 4 5 6 7 8 9 10 11
{
"name": "eleventy-repoviewer-example-site",
"version": "1.0.0",
"description": "A sample 11ty site that uses eleventy-plugin-repoviewer to display a git repository. In this case, the git repository being displayed is eleventy-plugin-repoviewer itself.",
"license": "ISC",
"author": "Tucker McKnight",
"type": "module",
"dependencies": {
"@11ty/eleventy": "^3.1.2",
"@11ty/eleventy-plugin-rss": "^2.0.4",
"@11ty/eleventy-plugin-syntaxhighlight": "^5.0.2",
"eleventy-plugin-repoviewer": "file:../../"
}
}
-1 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
import { test, expect } from '@playwright/test';
test('has title', async ({ page }) => {
await page.goto('http://localhost:8081/repos');
// Expect a title "to contain" a substring.
await expect(page).toHaveTitle("Repositories");
});
test('has links to repos and shows clone URL', async ({ page }) => {
await page.goto('http://localhost:8081/repos');
await expect(
page.getByText('https://localhost:8080/repos/git-static-site-ui.git')
).toBeVisible();
// Go to the first repo on the page
await page.getByRole('link', { name: 'Go to site' }).click();
// Expects page to have a heading with the name of Installation.
await expect(page.getByRole('heading', { name: 'git static site UI' })).toBeVisible();
});
test('the light mode/dark mode switch works', async ({ page }) => {
await page.goto('http://localhost:8081/repos');
// click light mode
(await page.waitForSelector("#dark-mode-switch")).click();
await page.getByRole('button', { name: /Light/ }).click();
const body = await page.locator('body');
expect(body).toHaveCSS('background-color', "rgb(239, 242, 228)");
// click dark mode
(await page.waitForSelector("#dark-mode-switch")).click();
await page.getByRole('button', { name: /Dark/ }).click();
expect(body).toHaveCSS('background-color', "rgb(8, 15, 53)");
});17 18 19 20 21
"mithril-node-render": "^3.0.2"
},
"devDependencies": {
"@types/node": "^24.0.7",
"bootstrap": "^5.3.8",
"rollup": "^4.57.1",17 18 19 20 21 22
"mithril-node-render": "^3.0.2"
},
"devDependencies": {
"@playwright/test": "^1.63.0",
"@types/node": "^24.0.7",
"bootstrap": "^5.3.8",
"rollup": "^4.57.1",430 431 432 433 434
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@popperjs/core": {
"version": "2.11.8",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@playwright/test": {
"version": "1.63.0",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.63.0.tgz",
"integrity": "sha512-oxMK4vllB9RK5NQ2l1pq1IfOf2AvnEuj/vYGDj0H2nMtmtZpKtCwt/l00GEO6xjGfpBNAvjovvYdCm50dRQkpQ==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"playwright": "1.63.0"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=20"
}
},
"node_modules/@popperjs/core": {
"version": "2.11.8",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",551 552 553 554 555 556 557 558
"arm64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [551 552 553 554 555
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [571 572 573 574 575 576 577 578
"arm64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [571 572 573 574 575
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [591 592 593 594 595 596 597 598
"ppc64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [591 592 593 594 595
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [611 612 613 614 615 616 617 618
"s390x"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [611 612 613 614 615
"s390x"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [631 632 633 634 635 636 637 638
"x64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [631 632 633 634 635
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [651 652 653 654 655 656 657 658
"x64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [651 652 653 654 655
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [1922 1923 1924 1925 1926 1927 1928 1929
"arm64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MPL-2.0",
"optional": true,
"os": [1922 1923 1924 1925 1926
"arm64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [1946 1947 1948 1949 1950 1951 1952 1953
"arm64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MPL-2.0",
"optional": true,
"os": [1946 1947 1948 1949 1950
"arm64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [1970 1971 1972 1973 1974 1975 1976 1977
"x64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MPL-2.0",
"optional": true,
"os": [1970 1971 1972 1973 1974
"x64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [1994 1995 1996 1997 1998 1999 2000 2001
"x64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MPL-2.0",
"optional": true,
"os": [1994 1995 1996 1997 1998
"x64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [2279 2280 2281 2282 2283
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/postcss": {
"version": "8.5.26",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz",2279 2280 2281 2282 2283 2284 2285 2286 2287 2288 2289 2290 2291 2292 2293 2294 2295 2296 2297 2298 2299 2300 2301 2302 2303 2304 2305 2306 2307 2308 2309 2310 2311 2312
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/playwright": {
"version": "1.63.0",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.63.0.tgz",
"integrity": "sha512-+7ziBLidS4NaNCdt57SUDT+wYmmd5fmiQejUic/kb+YsYSCPyOOE9sebzMjNmQrsnNpDJqd4WHvV/8lfKfUDUg==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "1.63.0"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=20"
}
},
"node_modules/playwright-core": {
"version": "1.63.0",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.63.0.tgz",
"integrity": "sha512-rYCsBF/M5HjUch52bbtVONEFjv6Xu8sm8h72dNlR5bzIE1fvC/bxgspzkjSfU+MweEMmPM8KJebG6nnyxo5mCg==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"playwright-core": "cli.js"
},
"engines": {
"node": ">=20"
}
},
"node_modules/postcss": {
"version": "8.5.26",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz",17 18 19 20 21
"license": "ISC",
"description": "",
"devDependencies": {
"@types/node": "^24.0.7",
"bootstrap": "^5.3.8",
"rollup": "^4.57.1",17 18 19 20 21 22
"license": "ISC",
"description": "",
"devDependencies": {
"@playwright/test": "^1.63.0",
"@types/node": "^24.0.7",
"bootstrap": "^5.3.8",
"rollup": "^4.57.1",
-1 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
import { defineConfig, devices } from '@playwright/test';
/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
// import dotenv from 'dotenv';
// import path from 'path';
// dotenv.config({ path: path.resolve(__dirname, '.env') });
/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: './browser_tests/tests',
/* 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: {
/* Base URL to use in actions like `await page.goto('')`. */
// baseURL: 'http://localhost:3000',
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
},
/* Configure projects for major browsers */
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},
/* Test against mobile viewports. */
// {
// name: 'Mobile Chrome',
// use: { ...devices['Pixel 5'] },
// },
// {
// name: 'Mobile Safari',
// use: { ...devices['iPhone 12'] },
// },
/* Test against branded browsers. */
// {
// name: 'Microsoft Edge',
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
// },
// {
// name: 'Google Chrome',
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
// },
],
/* Run your local dev server before starting the tests */
// webServer: {
// command: 'npm run start',
// url: 'http://localhost:3000',
// reuseExistingServer: !process.env.CI,
// },
});6 7 8 9 10 11 12 13 14 15 16
"module": "node18",
"target": "es6",
"lib": ["esnext", "dom"],
"allowJs": true,
"outDir": "dist"
},
"include": ["**/*.ts"],
"exclude": [
"dist/**/*",
"ts/frontend/**/*",
],
}6 7 8 9 10 11 12 13 14 15 16
"module": "node18",
"target": "es6",
"lib": ["esnext", "dom"],
"outDir": "dist"
},
"include": ["**/*.ts"],
"exclude": [
"dist/**/*",
"ts/frontend/**/*",
"browser_tests/**/*"
],
}