Skip to content

Commit 659dcf5

Browse files
committed
Add Playwright-based UI tests
Playwright (https://playwright.dev/) is a quite complete framework to perform UI tests on websites. We use this framework here to verify that: - the search works - switching between languages works - following links from a translated manual page fall back to English if the linked-to manual page has no translation - navigating between book sections colorizes the correct links in the drop-down A new feature of the Hugo/Pagefind-based site is that the search facility is language-aware. For this reason, we also verify that: - search results are language-dependent These tests can be run by first installing the `@playwright/test` dependency, via `npm install`, and then invoking `npx playwright test`. Helped-extensively-by: Max Schmitt <[email protected]> Signed-off-by: Johannes Schindelin <[email protected]>
1 parent cc86475 commit 659dcf5

File tree

5 files changed

+192
-0
lines changed

5 files changed

+192
-0
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,7 @@
1010
/.hugo_build.lock
1111
/public/
1212
/resources/_gen/
13+
/package-lock.json
14+
/node_modules/
15+
/test-results/
16+
/playwright-report/

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,10 @@ The [list of GUI clients](https://git-scm.com/downloads/guis) has been construct
164164

165165
* https://lychee.cli.rs/
166166

167+
### Playwright (website UI test framework)
168+
169+
* https://playwright.dev/
170+
167171
## License
168172

169173
The source code for the site is licensed under the MIT license, which you can find in

package.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"name": "git-scm.com",
3+
"version": "0.0.0",
4+
"description": "This is the Git home page.",
5+
"license": "MIT",
6+
"devDependencies": {
7+
"@playwright/test": "^1.47.0",
8+
"@types/node": "^22.5.4"
9+
}
10+
}

playwright.config.js

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// @ts-check
2+
const { defineConfig, devices } = require('@playwright/test');
3+
4+
/**
5+
* Read environment variables from file.
6+
* https://github.com/motdotla/dotenv
7+
*/
8+
// require('dotenv').config({ path: path.resolve(__dirname, '.env') });
9+
10+
/**
11+
* @see https://playwright.dev/docs/test-configuration
12+
*/
13+
module.exports = defineConfig({
14+
testDir: './tests',
15+
/* Run tests in files in parallel */
16+
fullyParallel: true,
17+
/* Fail the build on CI if you accidentally left test.only in the source code. */
18+
forbidOnly: !!process.env.CI,
19+
/* Retry on CI only */
20+
retries: process.env.CI ? 2 : 0,
21+
/* Opt out of parallel tests on CI. */
22+
workers: process.env.CI ? 1 : undefined,
23+
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
24+
reporter: 'html',
25+
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
26+
use: {
27+
/* Base URL to use in actions like `await page.goto('/')`. */
28+
// baseURL: 'http://127.0.0.1:3000',
29+
30+
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
31+
trace: 'on-first-retry',
32+
},
33+
34+
/* Configure projects for major browsers */
35+
projects: [
36+
{
37+
name: 'chromium',
38+
use: { ...devices['Desktop Chrome'] },
39+
},
40+
41+
{
42+
name: 'firefox',
43+
use: { ...devices['Desktop Firefox'] },
44+
},
45+
46+
{
47+
name: 'webkit',
48+
use: { ...devices['Desktop Safari'] },
49+
},
50+
51+
/* Test against mobile viewports. */
52+
// {
53+
// name: 'Mobile Chrome',
54+
// use: { ...devices['Pixel 5'] },
55+
// },
56+
// {
57+
// name: 'Mobile Safari',
58+
// use: { ...devices['iPhone 12'] },
59+
// },
60+
61+
/* Test against branded browsers. */
62+
// {
63+
// name: 'Microsoft Edge',
64+
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
65+
// },
66+
// {
67+
// name: 'Google Chrome',
68+
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
69+
// },
70+
],
71+
72+
/* Run your local dev server before starting the tests */
73+
// webServer: {
74+
// command: 'npm run start',
75+
// url: 'http://127.0.0.1:3000',
76+
// reuseExistingServer: !process.env.CI,
77+
// },
78+
});
79+

tests/git-scm.spec.js

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
const { test, expect, selectors } = require('@playwright/test')
2+
3+
const url = 'https://git.github.io/git-scm.com/'
4+
5+
test('search', async ({ page }) => {
6+
await page.goto(url)
7+
8+
// Search for "commit"
9+
const searchBox = page.getByPlaceholder('Type / to search entire site…')
10+
await searchBox.fill('commit')
11+
await searchBox.press('Shift')
12+
13+
// Expect the div to show up
14+
const showAllResults = page.getByText('Show all results...')
15+
await expect(showAllResults).toBeVisible()
16+
17+
// Expect the first search result to be "git-commit"
18+
const searchResults = page.locator('#search-results')
19+
await expect(searchResults.getByRole("link")).not.toHaveCount(0)
20+
await expect(searchResults.getByRole("link").nth(0)).toHaveText('git-commit')
21+
22+
// On localized pages, the search results should be localized as well
23+
await page.goto(`${url}docs/git-commit/fr`)
24+
await searchBox.fill('add')
25+
await searchBox.press('Shift')
26+
await expect(searchResults.getByRole("link").nth(0)).toHaveAttribute('href', /\/docs\/git-add\/fr(\.html)?$/)
27+
28+
// pressing the Enter key should navigate to the full search results page
29+
await searchBox.press('Enter')
30+
await expect(page).toHaveURL(/\/search.*language=fr/)
31+
})
32+
33+
test('manual pages', async ({ page }) => {
34+
await page.goto(`${url}docs/git-config`)
35+
36+
// The summary follows immediately after the heading "NAME", which is the first heading on the page
37+
const summary = page.locator('xpath=//h2/following-sibling::*[1]').first()
38+
await expect(summary).toHaveText('git-config - Get and set repository or global options')
39+
await expect(summary).toBeVisible()
40+
41+
// Verify that the drop-downs are shown when clicked
42+
const previousVersionDropdown = page.locator('#previous-versions-dropdown')
43+
await expect(previousVersionDropdown).toBeHidden()
44+
await page.getByRole('link', { name: 'Latest version' }).click()
45+
await expect(previousVersionDropdown).toBeVisible()
46+
47+
const topicsDropdown = page.locator('#topics-dropdown')
48+
await expect(topicsDropdown).toBeHidden()
49+
await page.getByRole('link', { name: 'Topics' }).click()
50+
await expect(topicsDropdown).toBeVisible()
51+
await expect(previousVersionDropdown).toBeHidden()
52+
53+
const languageDropdown = page.locator('#l10n-versions-dropdown')
54+
await expect(languageDropdown).toBeHidden()
55+
await page.getByRole('link', { name: 'English' }).click()
56+
await expect(languageDropdown).toBeVisible()
57+
await expect(topicsDropdown).toBeHidden()
58+
await expect(previousVersionDropdown).toBeHidden()
59+
60+
// Verify that the language is changed when a different language is selected
61+
await page.getByRole('link', { name: 'Français' }).click()
62+
await expect(summary).toHaveText('git-config - Lire et écrire les options du dépôt et les options globales')
63+
await expect(summary).not.toHaveText('git-config - Get and set repository or global options')
64+
65+
// links to other manual pages should stay within the language when possible,
66+
// but fall back to English if the page was not yet translated
67+
const gitRevisionsLink = page.getByRole('link', { name: 'gitrevisions[7]' })
68+
await expect(gitRevisionsLink).toBeVisible()
69+
await expect(gitRevisionsLink).toHaveAttribute('href', /\/docs\/gitrevisions\/fr$/)
70+
await gitRevisionsLink.click()
71+
await expect(page).toHaveURL(/\/docs\/gitrevisions$/)
72+
})
73+
74+
test('book', async ({ page }) => {
75+
await page.goto(`${url}book`)
76+
77+
// Navigate to the first section
78+
await page.getByRole('link', { name: 'Getting Started' }).click()
79+
await expect(page).toHaveURL(/Getting-Started-About-Version-Control/)
80+
81+
// Verify that the drop-down is shown when clicked
82+
const chaptersDropdown = page.locator('#chapters-dropdown')
83+
await expect(chaptersDropdown).toBeHidden()
84+
await page.getByRole('link', { name: 'Chapters' }).click()
85+
await expect(chaptersDropdown).toBeVisible()
86+
87+
// Only the current section is marked as active
88+
await expect(chaptersDropdown.getByRole('link', { name: /About Version Control/ })).toHaveClass(/active/)
89+
await expect(chaptersDropdown.locator('.active')).toHaveCount(1)
90+
91+
// Navigate to the French translation
92+
await page.getByRole('link', { name: 'Français' }).click()
93+
await expect(page).toHaveURL(/book\/fr/)
94+
await expect(page.getByRole('link', { name: 'Démarrage rapide' })).toBeVisible()
95+
})

0 commit comments

Comments
 (0)