diff --git a/.gitignore b/.gitignore index 08ab9aa33cc..5af44677660 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,13 @@ public/ .venv *~ **/.DS_Store -resources/ \ No newline at end of file + +# Playwright +node_modules/ +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ +test-results/ +resources/ + diff --git a/github-workflows-playwright.yml b/github-workflows-playwright.yml new file mode 100644 index 00000000000..3eb13143c3d --- /dev/null +++ b/github-workflows-playwright.yml @@ -0,0 +1,27 @@ +name: Playwright Tests +on: + push: + branches: [ main, master ] + pull_request: + branches: [ main, master ] +jobs: + test: + timeout-minutes: 60 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: lts/* + - name: Install dependencies + run: npm ci + - name: Install Playwright Browsers + run: npx playwright install --with-deps + - name: Run Playwright tests + run: npx playwright test + - uses: actions/upload-artifact@v4 + if: ${{ !cancelled() }} + with: + name: playwright-report + path: playwright-report/ + retention-days: 30 diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000000..69b6b1b839c --- /dev/null +++ b/package-lock.json @@ -0,0 +1,97 @@ +{ + "name": "nerdydaytrips", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "nerdydaytrips", + "version": "0.0.0", + "license": "UNLICENSED", + "devDependencies": { + "@playwright/test": "^1.53.1", + "@types/node": "^24.0.3" + } + }, + "node_modules/@playwright/test": { + "version": "1.53.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.53.1.tgz", + "integrity": "sha512-Z4c23LHV0muZ8hfv4jw6HngPJkbbtZxTkxPNIg7cJcTc9C28N/p2q7g3JZS2SiKBBHJ3uM1dgDye66bB7LEk5w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.53.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@types/node": { + "version": "24.0.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.3.tgz", + "integrity": "sha512-R4I/kzCYAdRLzfiCabn9hxWfbuHS573x+r0dJMkkzThEa7pbrcDWK+9zu3e7aBOouf+rQAciqPFMnxwr0aWgKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.8.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/playwright": { + "version": "1.53.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.53.1.tgz", + "integrity": "sha512-LJ13YLr/ocweuwxyGf1XNFWIU4M2zUSo149Qbp+A4cpwDjsxRPj7k6H25LBrEHiEwxvRbD8HdwvQmRMSvquhYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.53.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.53.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.53.1.tgz", + "integrity": "sha512-Z46Oq7tLAyT0lGoFx4DOuB1IA9D1TPj0QkYxpPVUnGDqHHvDpCftu1J2hM2PiWsNMoZh8+LQaarAWcDfPBc6zg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/undici-types": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", + "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000000..eb058f81016 --- /dev/null +++ b/package.json @@ -0,0 +1,21 @@ +{ + "name": "nerdydaytrips", + "version": "0.0.0", + "description": "nerdydaytrips", + "main": "no,js", + "scripts": {}, + "repository": { + "type": "git", + "url": "git+https://github.com/NerdyDayTrips/website.git" + }, + "author": "popey", + "license": "UNLICENSED", + "bugs": { + "url": "https://github.com/NerdyDayTrips/website/issues" + }, + "homepage": "https://github.com/NerdyDayTrips/website#readme", + "devDependencies": { + "@playwright/test": "^1.53.1", + "@types/node": "^24.0.3" + } +} diff --git a/playwright.config.js b/playwright.config.js new file mode 100644 index 00000000000..f329e6291cd --- /dev/null +++ b/playwright.config.js @@ -0,0 +1,83 @@ +// @ts-check +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: './tests', + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in t + he 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: 'list', + /* 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, + // }, +}); + diff --git a/run-tests.sh b/run-tests.sh new file mode 100644 index 00000000000..737ced4cc2c --- /dev/null +++ b/run-tests.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +echo "┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓" +echo "┃ .-----.-----.----.--| |.--.--. ┃" +echo "┃ | | -__| _| _ || | | ┃" +echo "┃ |__|__|_____|__| |_____||___ | ┃" +echo "┃ |_____| ┃" +echo "┃ .--| |.---.-.--.--.| |_.----.|__|.-----.-----. ┃" +echo "┃ | _ || _ | | || _| _|| || _ |__ --| ┃" +echo "┃ |_____||___._|___ ||____|__| |__|| __|_____| ┃" +echo "┃ |_____| |__| ┃" +echo "┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫" +echo "┃ t̶e̶s̶t̶s̶?̶ s̶w̶e̶e̶t̶!̶ ┃" +echo "┃ test suite ┃" +echo "┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛" + +# make sure everything we need is installed +# that is: playwright, which is our test runner +npm install --quiet +# and headless chrome to run tests in +npx playwright install chromium-headless-shell + +# now run hugo server and trap exit to kill it when the test suite finishes +# we run hugo server so that the test suite is using a real webserver +# otherwise web browsers are super whiny about file:// urls +# we could just hugo build and then run a different web server +# but we've got hugo around, might as well use it +# We skip opengraph image building so that the test suite doesn't take ages +# and we render in memory so that we don't need to write to disk +# 61276 is the seating capacity of Anfield #YNWA +echo Building testing website in $TEMPD +HUGO_SKIP_OPENGRAPH=true hugo server --renderToMemory --port 61276 & +trap "trap - SIGTERM && kill -- -$$" SIGINT SIGTERM EXIT + +# finally, run the tests +npx playwright test diff --git a/tests/basics.spec.js b/tests/basics.spec.js new file mode 100644 index 00000000000..915a79e06e7 --- /dev/null +++ b/tests/basics.spec.js @@ -0,0 +1,19 @@ +// @ts-check +import { test, expect } from '@playwright/test'; + +test('has title', async ({ page }) => { + await page.goto('http://localhost:61276/'); + + // Expect a title "to contain" a substring. + await expect(page).toHaveTitle(/Nerdy Day Trips/); +}); + +test('about link', async ({ page }) => { + await page.goto('http://localhost:61276/'); + + // Click the get started link. + await page.getByRole('link', { name: 'About' }).click(); + + // Expects page to have an About heading. + await expect(page.locator("#post-header").getByRole('heading', { name: 'About Nerdy Day Trips' })).toBeVisible(); +}); diff --git a/themes/ndt2/layouts/partials/opengraph.html b/themes/ndt2/layouts/partials/opengraph.html index e88f992a87f..e871c9614d5 100644 --- a/themes/ndt2/layouts/partials/opengraph.html +++ b/themes/ndt2/layouts/partials/opengraph.html @@ -24,6 +24,8 @@ {{ $img := resources.Get "images/og.png" }} {{ if not $img }} {{ warnf "OG base image not found!" }} + {{ else if getenv "HUGO_SKIP_OPENGRAPH" }} + {{ warnf "Skipping opengraph image gen because HUGO_SKIP_OPENGRAPH is set" }} {{ else }} {{ $boldFont := resources.Get "fonts/Ubuntu-B.ttf"}} {{ $mediumFont := resources.Get "fonts/Ubuntu-R.ttf"}}