Skip to content

Commit

Permalink
Merge branch 'playwright'
Browse files Browse the repository at this point in the history
This topic brings in some UI testing, to build confidence not only in
the migration of the website, but also to be able to prevent (or at
least swiftly detect) regressions.

Signed-off-by: Johannes Schindelin <[email protected]>
  • Loading branch information
dscho committed Sep 10, 2024
2 parents 246cec0 + b43a124 commit 063d580
Show file tree
Hide file tree
Showing 5 changed files with 291 additions and 0 deletions.
22 changes: 22 additions & 0 deletions .github/workflows/playwright.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: Playwright Tests
on:
pull_request:
workflow_dispatch:
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 Playwright Browsers
run: npx playwright install --with-deps
- name: Run Playwright tests
run: npx playwright test
- uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: playwright-report/
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,7 @@
/.hugo_build.lock
/public/
/resources/_gen/
/package-lock.json
/node_modules/
/test-results/
/playwright-report/
10 changes: 10 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "git-scm.com",
"version": "0.0.0",
"description": "This is the Git home page.",
"license": "MIT",
"devDependencies": {
"@playwright/test": "^1.47.0",
"@types/node": "^22.5.4"
}
}
79 changes: 79 additions & 0 deletions playwright.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// @ts-check
const { defineConfig, devices } = require('@playwright/test');

/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
// require('dotenv').config({ path: path.resolve(__dirname, '.env') });

/**
* @see https://playwright.dev/docs/test-configuration
*/
module.exports = defineConfig({
testDir: './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://127.0.0.1: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://127.0.0.1:3000',
// reuseExistingServer: !process.env.CI,
// },
});

176 changes: 176 additions & 0 deletions tests/git-scm.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
const { test, expect, selectors } = require('@playwright/test')

const url = 'https://git-scm.com/'
// const url = 'https://git.github.io/git-scm.com/'
const isRailsApp = url === 'https://git-scm.com/'

async function pretendPlatform(page, platform) {
await page.context().addInitScript({
content: `Object.defineProperty(navigator, 'platform', { get: () => '${platform}' })`
})
}

test.describe('Windows', () => {
test.use({
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36',
})

test('download/GUI links', async ({ page }) => {
pretendPlatform(page, 'Windows')
await page.goto(url)
await expect(page.getByRole('link', { name: 'Download for Windows' })).toBeVisible()

await expect(page.getByRole('link', { name: 'Graphical UIs' })).not.toBeVisible()
const windowsGUIs = page.getByRole('link', { name: 'Windows GUIs' })
await expect(windowsGUIs).toBeVisible()
await expect(windowsGUIs).toHaveAttribute('href', /\/download\/gui\/windows$/)

// navigate to Windows GUIs
await windowsGUIs.click()
const windowsButton = page.getByRole('link', { name: 'Windows' })
await expect(windowsButton).toBeVisible()
await expect(windowsButton).toHaveClass(/selected/)

const allButton = page.getByRole('link', { name: 'All' })
await expect(allButton).not.toHaveClass(/selected/)

const thumbnails = page.locator('.gui-thumbnails li:visible')
const count = await thumbnails.count()
await allButton.click()
await expect.poll(() => thumbnails.count()).toBeGreaterThan(count)
})
})

test.describe('macOS', () => {
test.use({
userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_6_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.6 Safari/605.1.15',
})

test('download/GUI links', async ({ page }) => {
pretendPlatform(page, 'Mac OS X')
await page.goto(url)
await expect(page.getByRole('link', { name: 'Download for Mac' })).toBeVisible()

await expect(page.getByRole('link', { name: 'Graphical UIs' })).not.toBeVisible()
await expect(page.getByRole('link', { name: 'Mac GUIs' })).toBeVisible()
})
})

test.describe('Linux', () => {
test.use({
userAgent: 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:130.0) Gecko/20100101 Firefox/130.0',
})

test('download/GUI links', async ({ page }) => {
pretendPlatform(page, 'Linux')
await page.goto(url)
await expect(page.getByRole('link', { name: 'Download for Linux' })).toBeVisible()

await expect(page.getByRole('link', { name: 'Graphical UIs' })).not.toBeVisible()
await expect(page.getByRole('link', { name: 'Linux GUIs' })).toBeVisible()
})
})

test('search', async ({ page }) => {
await page.goto(url)

// Search for "commit"
const searchBox = page.getByPlaceholder('Type / to search entire site…')
await searchBox.fill('commit')
await searchBox.press('Shift')

// Expect the div to show up
const showAllResults = page.getByText('Show all results...')
await expect(showAllResults).toBeVisible()

// Expect the first search result to be "git-commit"
const searchResults = page.locator('#search-results')
await expect(searchResults.getByRole("link")).not.toHaveCount(0)
await expect(searchResults.getByRole("link").nth(0)).toHaveText('git-commit')

// On localized pages, the search results should be localized as well
await page.goto(`${url}docs/git-commit/fr`)
await searchBox.fill('add')
await searchBox.press('Shift')
if (isRailsApp) {
await expect(searchResults.getByRole("link").nth(0)).toHaveAttribute('href', /\/docs\/git-add$/)
} else {
await expect(searchResults.getByRole("link").nth(0)).toHaveAttribute('href', /\/docs\/git-add\/fr(\.html)?$/)
}

// pressing the Enter key should navigate to the full search results page
await searchBox.press('Enter')
if (isRailsApp) {
await expect(page).toHaveURL(/\/search/)
} else {
await expect(page).toHaveURL(/\/search.*language=fr/)
}
})

test('manual pages', async ({ page }) => {
await page.goto(`${url}docs/git-config`)

// The summary follows immediately after the heading "NAME", which is the first heading on the page
const summary = page.locator('xpath=//h2/following-sibling::*[1]').first()
await expect(summary).toHaveText('git-config - Get and set repository or global options')
await expect(summary).toBeVisible()

// Verify that the drop-downs are shown when clicked
const previousVersionDropdown = page.locator('#previous-versions-dropdown')
await expect(previousVersionDropdown).not.toBeVisible()
if (isRailsApp) {
await page.getByRole('link', { name: /Version \d+\.\d+\.\d+/ }).click()
} else {
await page.getByRole('link', { name: 'Latest version' }).click()
}
await expect(previousVersionDropdown).toBeVisible()

const topicsDropdown = page.locator('#topics-dropdown')
await expect(topicsDropdown).not.toBeVisible()
await page.getByRole('link', { name: 'Topics' }).click()
await expect(topicsDropdown).toBeVisible()
await expect(previousVersionDropdown).not.toBeVisible()

const languageDropdown = page.locator('#l10n-versions-dropdown')
await expect(languageDropdown).not.toBeVisible()
await page.getByRole('link', { name: 'English' }).click()
await expect(languageDropdown).toBeVisible()
await expect(topicsDropdown).not.toBeVisible()
await expect(previousVersionDropdown).not.toBeVisible()

// Verify that the language is changed when a different language is selected
await page.getByRole('link', { name: 'Français' }).click()
await expect(summary).toHaveText('git-config - Lire et écrire les options du dépôt et les options globales')
await expect(summary).not.toHaveText('git-config - Get and set repository or global options')

// links to other manual pages should stay within the language when possible,
// but fall back to English if the page was not yet translated
const gitRevisionsLink = page.getByRole('link', { name: 'gitrevisions[7]' })
await expect(gitRevisionsLink).toBeVisible()
await expect(gitRevisionsLink).toHaveAttribute('href', /\/docs\/gitrevisions\/fr$/)
gitRevisionsLink.click()
await expect(page).toHaveURL(/\/docs\/gitrevisions$/)
})

test('book', async ({ page }) => {
await page.goto(`${url}book`)

// Navigate to the first section
await page.getByRole('link', { name: 'Getting Started' }).click()
await expect(page).toHaveURL(/Getting-Started-About-Version-Control/)

// Verify that the drop-down is shown when clicked
const chaptersDropdown = page.locator('#chapters-dropdown')
await expect(chaptersDropdown).not.toBeVisible()
await page.getByRole('link', { name: 'Chapters' }).click()
await expect(chaptersDropdown).toBeVisible()

// Only the current section is marked as active
await expect(chaptersDropdown.getByRole('link', { name: /About Version Control/ })).toHaveClass(/active/)
await expect(chaptersDropdown.locator('.active')).toHaveCount(1)

// Navigate to the French translation
await page.getByRole('link', { name: 'Français' }).click()
await expect(page).toHaveURL(/book\/fr/)
await expect(page.getByRole('link', { name: 'Démarrage rapide' })).toBeVisible()
})

0 comments on commit 063d580

Please sign in to comment.