Skip to content

Commit

Permalink
Add Playwright-based UI tests
Browse files Browse the repository at this point in the history
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]>
  • Loading branch information
dscho committed Sep 20, 2024
1 parent cc86475 commit 659dcf5
Show file tree
Hide file tree
Showing 5 changed files with 192 additions and 0 deletions.
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/
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,10 @@ The [list of GUI clients](https://git-scm.com/downloads/guis) has been construct

* https://lychee.cli.rs/

### Playwright (website UI test framework)

* https://playwright.dev/

## License

The source code for the site is licensed under the MIT license, which you can find in
Expand Down
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,
// },
});

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

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

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')
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')
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).toBeHidden()
await page.getByRole('link', { name: 'Latest version' }).click()
await expect(previousVersionDropdown).toBeVisible()

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

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

// 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$/)
await 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).toBeHidden()
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 659dcf5

Please sign in to comment.