-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
Showing
5 changed files
with
291 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
// }, | ||
}); | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
}) |