e2e: reduce boilerplate and add additonal tests

- Drys up e2e test logic and adds additional
tests to mirror the compare pages set.
- Pins some unit page urls to specific versions
to reduce the frequency of updates.
- Parallelizes mobile and desktop tests to improve
performance. In my experience, overall test run takes
on average one minute.

Change-Id: I04ebd109bf2f82c2a570757fe9c124f87c092147
Reviewed-on: https://go-review.googlesource.com/c/pkgsite/+/319971
Reviewed-by: Julie Qiu <julie@golang.org>
Trust: Jamal Carvalho <jamal@golang.org>
This commit is contained in:
Jamal Carvalho 2021-05-13 19:11:28 -04:00
Родитель 06648a580a
Коммит 9c0705e7cc
29 изменённых файлов: 632 добавлений и 648 удалений

Просмотреть файл

@ -13,3 +13,5 @@ overrides:
- files:
- "*.test.ts"
extends: plugin:jest/recommended
rules:
jest/no-standalone-expect: off

Просмотреть файл

@ -1,46 +0,0 @@
/**
* @license
* Copyright 2021 The Go Authors. All rights reserved.
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/
import './global-types';
import puppeteer, { Page } from 'puppeteer';
let page: Page;
beforeAll(async () => {
page = await newPage();
await page.goto(baseURL + '/badge');
});
afterAll(async () => {
await page.close();
});
test('accessibility tree (desktop)', async () => {
const a11yTree = await page.accessibility.snapshot();
expect(a11yTree).toMatchSnapshot();
});
test('full page (desktop)', async () => {
const image = await page.screenshot({ fullPage: true });
expect(image).toMatchImageSnapshot();
});
test('accessibility tree (mobile)', async () => {
await page.emulate(puppeteer.devices['Pixel 2']);
const a11yTree = await page.accessibility.snapshot();
expect(a11yTree).toMatchSnapshot();
});
test('full page (mobile)', async () => {
await page.emulate(puppeteer.devices['Pixel 2']);
const image = await page.screenshot({ fullPage: true });
expect(image).toMatchImageSnapshot();
});
test('no page errors', () => {
expect(pageErrors).toHaveLength(0);
});

40
e2e/basic.desktop.test.ts Normal file
Просмотреть файл

@ -0,0 +1,40 @@
/**
* @license
* Copyright 2020 The Go Authors. All rights reserved.
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/
import { Page } from 'puppeteer';
import './global-types';
import { testcases } from './basic.testcases';
import * as pg from './helpers/page';
let page: Page;
beforeAll(async () => {
page = await pg.newPage();
});
afterAll(async () => {
await page.close();
});
testcases('$name accessibility tree', async ({ path, prepare }) => {
await page.goto(path);
if (prepare) await prepare(page);
const a11yTree = await page.accessibility.snapshot();
expect(a11yTree).toMatchSnapshot();
});
testcases('$name screenshot', async ({ path, prepare }) => {
await page.goto(path);
if (prepare) await prepare(page);
const image = await page.screenshot({ fullPage: true });
expect(image).toMatchImageSnapshot();
});
test('no page errors', () => {
expect(pageErrors).toHaveLength(0);
});

41
e2e/basic.mobile.test.ts Normal file
Просмотреть файл

@ -0,0 +1,41 @@
/**
* @license
* Copyright 2020 The Go Authors. All rights reserved.
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/
import { Page } from 'puppeteer';
import './global-types';
import { testcases } from './basic.testcases';
import * as pg from './helpers/page';
let page: Page;
beforeAll(async () => {
page = await pg.newPage();
await page.setViewport({ width: 411, height: 731 });
});
afterAll(async () => {
await page.close();
});
testcases('$name accessibility tree', async ({ path, prepare }) => {
await page.goto(path);
if (prepare) await prepare(page);
const a11yTree = await page.accessibility.snapshot();
expect(a11yTree).toMatchSnapshot();
});
testcases('$name screenshot', async ({ path, prepare }) => {
await page.goto(path);
if (prepare) await prepare(page);
const image = await page.screenshot({ fullPage: true });
expect(image).toMatchImageSnapshot();
});
test('no page errors', () => {
expect(pageErrors).toHaveLength(0);
});

20
e2e/basic.testcases.ts Normal file
Просмотреть файл

@ -0,0 +1,20 @@
/**
* @license
* Copyright 2020 The Go Authors. All rights reserved.
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/
import * as pg from './helpers/page';
import * as search from './helpers/search.page';
export const testcases = test.each`
name | path | prepare
${'badge'} | ${'/badge'} | ${pg.prepare}
${'error'} | ${'/bad.package@v1.0-badversion'} | ${pg.prepare}
${'fetch'} | ${'/fetch.test'} | ${pg.prepare}
${'home'} | ${'/'} | ${pg.prepare}
${'license policy'} | ${'/license-policy'} | ${pg.prepare}
${'search'} | ${'/search?q=http'} | ${search.prepare}
${'search help'} | ${'/search-help'} | ${pg.prepare}
`;

Просмотреть файл

@ -1,46 +0,0 @@
/**
* @license
* Copyright 2021 The Go Authors. All rights reserved.
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/
import './global-types';
import puppeteer, { Page } from 'puppeteer';
let page: Page;
beforeAll(async () => {
page = await newPage();
await page.goto(baseURL + '/bad.package@v1.0-badversion');
});
afterAll(async () => {
await page.close();
});
test('accessibility tree (desktop)', async () => {
const a11yTree = await page.accessibility.snapshot();
expect(a11yTree).toMatchSnapshot();
});
test('full page (desktop)', async () => {
const image = await page.screenshot({ fullPage: true });
expect(image).toMatchImageSnapshot();
});
test('accessibility tree (mobile)', async () => {
await page.emulate(puppeteer.devices['Pixel 2']);
const a11yTree = await page.accessibility.snapshot();
expect(a11yTree).toMatchSnapshot();
});
test('full page (mobile)', async () => {
await page.emulate(puppeteer.devices['Pixel 2']);
const image = await page.screenshot({ fullPage: true });
expect(image).toMatchImageSnapshot();
});
test('no page errors', () => {
expect(pageErrors).toHaveLength(0);
});

Просмотреть файл

@ -0,0 +1,65 @@
/**
* @license
* Copyright 2021 The Go Authors. All rights reserved.
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/
import { Page } from 'puppeteer';
import './global-types';
import * as pg from './helpers/page';
import * as unit from './helpers/unit.page';
let page: Page;
beforeAll(async () => {
page = await pg.newPage();
await page.goto('/errors@go1.16.3');
await unit.prepare(page);
});
afterAll(async () => {
await page.close();
});
test.each`
href
${'#section-documentation'}
${'#pkg-overview'}
${'#pkg-index'}
${'#pkg-constants'}
${'#pkg-variables'}
${'#pkg-functions'}
${'#As'}
${'#pkg-types'}
${'#section-sourcefiles'}
`('doc outline $href', async ({ href }) => {
await page.click(`[href="${href}"][role="treeitem"]`);
const image = await page.screenshot();
expect(image).toMatchImageSnapshot();
});
describe('jump to modal', () => {
test('opens', async () => {
await page.click(pg.select('jump-to-button'));
const expanded = await page.screenshot();
expect(expanded).toMatchImageSnapshot();
});
test('searches identifiers on input', async () => {
await page.keyboard.type('Wrap');
const inputWrap = await page.screenshot();
expect(inputWrap).toMatchImageSnapshot();
});
test('jumps to selected identifier', async () => {
await page.keyboard.press('Enter');
const wrap = await page.screenshot();
expect(wrap).toMatchImageSnapshot();
});
});
test('no page errors', () => {
expect(pageErrors).toHaveLength(0);
});

Просмотреть файл

@ -1,46 +0,0 @@
/**
* @license
* Copyright 2020 The Go Authors. All rights reserved.
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/
import './global-types';
import puppeteer, { Page } from 'puppeteer';
let page: Page;
beforeAll(async () => {
page = await newPage();
await page.goto(baseURL + '/fetch.test');
});
afterAll(async () => {
await page.close();
});
test('accessibility tree (desktop)', async () => {
const a11yTree = await page.accessibility.snapshot();
expect(a11yTree).toMatchSnapshot();
});
test('full page (desktop)', async () => {
const image = await page.screenshot({ fullPage: true });
expect(image).toMatchImageSnapshot();
});
test('accessibility tree (mobile)', async () => {
await page.emulate(puppeteer.devices['Pixel 2']);
const a11yTree = await page.accessibility.snapshot();
expect(a11yTree).toMatchSnapshot();
});
test('full page (mobile)', async () => {
await page.emulate(puppeteer.devices['Pixel 2']);
const image = await page.screenshot({ fullPage: true });
expect(image).toMatchImageSnapshot();
});
test('no page errors', () => {
expect(pageErrors).toHaveLength(0);
});

Просмотреть файл

@ -5,7 +5,7 @@
* license that can be found in the LICENSE file.
*/
import { Browser, Page } from 'puppeteer';
import { Browser } from 'puppeteer';
/**
* global declares global variables available in e2e test files.
@ -17,18 +17,7 @@ declare global {
const browser: Browser;
/**
* The baseURL for pkgsite pages (e.g., https://staging-pkg.go.dev).
*/
const baseURL: string;
/**
* newPage resolves to a new Page object. The Page object provides methods to
* interact with a single Chrome tab.
*/
const newPage: () => Promise<Page>;
/**
* pageErrors is a record of uncaught exceptions that have occured on a page.
* pageErrors is a record of uncaught exceptions that have occured in a test suite.
*/
const pageErrors: Error[];
}

107
e2e/helpers/page.ts Normal file
Просмотреть файл

@ -0,0 +1,107 @@
/**
* @license
* Copyright 2021 The Go Authors. All rights reserved.
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/
import { DirectNavigationOptions, Page } from 'puppeteer';
import '../global-types';
const { AUTHORIZATION = null, BASE_URL = 'http://host.docker.internal:8080' } = process.env;
/**
* newPage opens a new chrome tab, sets up a request intercept
* to provide an authorization header for relevant requests, and
* prefixes page.goto urls with the base URL for the test current
* environment.
* @returns a new page context
*/
export async function newPage(): Promise<Page> {
const page = await browser.newPage();
if (AUTHORIZATION) {
await page.setRequestInterception(true);
page.on('request', r => {
const url = new URL(r.url());
let headers = r.headers();
if (url.origin === BASE_URL) {
headers = { ...r.headers(), Authorization: `Bearer ${AUTHORIZATION}` };
}
r.continue({ headers });
});
}
page.on('pageerror', err => {
this.global.pageErrors.push(err);
});
const go = page.goto;
page.goto = (path: string, opts?: DirectNavigationOptions) =>
go.call(page, BASE_URL + path, opts);
return page;
}
/**
* select will create a data-test-id attribute selector for a given test id.
* @param testId the test id of the element to select.
* @param rest a place to add combinators and additional selectors.
* @returns an attribute selector.
*/
export function select(testId: string, rest = ''): string {
return `[data-test-id="${testId}"] ${rest}`;
}
/**
* prepare disables page transitions and animations.
* @param page The page to prepare
*/
export async function prepare(page: Page): Promise<void> {
await Promise.all([
page.addStyleTag({
content: `
*,
*::after,
*::before {
transition-delay: 0s !important;
transition-duration: 0s !important;
animation-delay: -0.0001s !important;
animation-duration: 0s !important;
animation-play-state: paused !important;
caret-color: transparent;
}`,
}),
]);
}
/**
* $eval wraps page.$eval to check if an element exists before
* attempting to run the callback.
* @param page the current page
* @param selector a css selector
* @param cb an operation to perform on the selected element
*/
export async function $eval(
page: Page,
selector: string,
cb?: (el: Element) => unknown
): Promise<void> {
if (await page.$(selector)) {
await page.$eval(selector, cb);
}
}
/**
* $$eval wraps page.$$eval to check if an element exists before
* attempting to run the callback.
* @param page the current page
* @param selector a css selector
* @param cb an operation to perform on an array of the selected elements
*/
export async function $$eval(
page: Page,
selector: string,
cb?: (els: Element[]) => unknown
): Promise<void> {
if (await page.$(selector)) {
await page.$$eval(selector, cb);
}
}

Просмотреть файл

@ -0,0 +1,30 @@
import { Page } from 'puppeteer';
import * as pg from './page';
import * as unit from './unit.page';
/**
* prepare gets the pkgsite module page ready for snapshot tests by rewriting highly
* variable page content to constant values.
* @param page The page to prepare
*/
export async function prepare(page: Page): Promise<void> {
await unit.prepare(page);
await Promise.all([
pg.$eval(
page,
pg.select('UnitHeader-version', 'a'),
el =>
((el as HTMLElement).innerHTML =
'<span class="UnitHeader-detailItemSubtle">Version: </span>v0.0.0')
),
pg.$eval(
page,
pg.select('UnitHeader-commitTime'),
el => ((el as HTMLElement).innerHTML = 'Published: Apr 16, 2021')
),
pg.$$eval(page, pg.select('UnitHeader-imports', 'a'), els =>
els.map(el => (el.innerHTML = 'Imports: 0'))
),
]);
}

Просмотреть файл

@ -0,0 +1,46 @@
/**
* @license
* Copyright 2021 The Go Authors. All rights reserved.
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/
import { Page } from 'puppeteer';
import * as pg from './page';
/**
* prepare gets the search page ready for snapshot tests by rewriting highly
* variable page content to constant values.
* @param page The page to prepare
*/
export async function prepare(page: Page): Promise<void> {
await pg.prepare(page);
await Promise.all([
pg.$$eval(page, '[data-test-id="snippet-title"]', els =>
els.map(el => {
el.innerHTML = 'net/http/pprof';
(el as HTMLAnchorElement).href = 'net/http/pprof';
})
),
pg.$$eval(page, '[data-test-id="snippet-synopsis"]', els =>
els.map(el => {
el.innerHTML =
'Package pprof serves via its HTTP server runtime profiling ' +
'data in the format expected by the pprof visualization tool.';
})
),
pg.$$eval(page, '[data-test-id="snippet-version"]', els =>
els.map(el => (el.innerHTML = 'go1.16.3'))
),
pg.$$eval(page, '[data-test-id="snippet-published"]', els =>
els.map(el => (el.innerHTML = 'Apr 1, 2021'))
),
pg.$$eval(page, '[data-test-id="snippet-importedby"]', els =>
els.map(el => (el.innerHTML = '11632'))
),
pg.$$eval(page, '[data-test-id="snippet-license"]', els =>
els.map(el => (el.innerHTML = 'BSD-3-Clause'))
),
]);
}

27
e2e/helpers/unit.page.ts Normal file
Просмотреть файл

@ -0,0 +1,27 @@
/**
* @license
* Copyright 2021 The Go Authors. All rights reserved.
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/
import { Page } from 'puppeteer';
import * as pg from './page';
/**
* prepare gets the unit page ready for snapshot testing by changing the
* imported by count to zero and hiding the header and footer to simplify
* snapshot diffing.
* @param page The page to prepare
*/
export async function prepare(page: Page): Promise<void> {
await pg.prepare(page);
await Promise.all([
pg.$$eval(page, pg.select('UnitHeader-importedby', 'a'), els =>
els.map(el => (el.innerHTML = 'Imported by: 0'))
),
pg.$eval(page, '.Site-header', el => ((el as HTMLElement).style.visibility = 'hidden')),
pg.$eval(page, '.Site-footer', el => ((el as HTMLElement).style.visibility = 'hidden')),
]);
}

Просмотреть файл

@ -1,48 +0,0 @@
/**
* @license
* Copyright 2020 The Go Authors. All rights reserved.
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/
import './global-types';
import puppeteer, { Page } from 'puppeteer';
let page: Page;
beforeAll(async () => {
page = await newPage();
await page.goto(baseURL);
});
afterAll(async () => {
await page.close();
});
test('accessibility tree (desktop)', async () => {
const a11yTree = await page.accessibility.snapshot();
expect(a11yTree).toMatchSnapshot();
});
test('full page (desktop)', async () => {
await page.$eval('[data-test-id="homepage-search"]', e => (e as HTMLInputElement).blur());
const image = await page.screenshot({ fullPage: true });
expect(image).toMatchImageSnapshot();
});
test('accessibility tree (mobile)', async () => {
await page.emulate(puppeteer.devices['Pixel 2']);
const a11yTree = await page.accessibility.snapshot();
expect(a11yTree).toMatchSnapshot();
});
test('full page (mobile)', async () => {
await page.emulate(puppeteer.devices['Pixel 2']);
await page.$eval('[data-test-id="homepage-search"]', e => (e as HTMLInputElement).blur());
const image = await page.screenshot({ fullPage: true });
expect(image).toMatchImageSnapshot();
});
test('no page errors', () => {
expect(pageErrors).toHaveLength(0);
});

Просмотреть файл

@ -1,45 +0,0 @@
/**
* @license
* Copyright 2021 The Go Authors. All rights reserved.
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/
import './global-types';
import puppeteer, { Page } from 'puppeteer';
let page: Page;
beforeAll(async () => {
page = await newPage();
await page.goto(baseURL + '/license-policy');
});
afterAll(async () => {
await page.close();
});
test('accessibility tree (desktop)', async () => {
const a11yTree = await page.accessibility.snapshot();
expect(a11yTree).toMatchSnapshot();
});
test('full page (desktop)', async () => {
const image = await page.screenshot({ fullPage: true });
expect(image).toMatchImageSnapshot();
});
test('accessibility tree (mobile)', async () => {
const a11yTree = await page.accessibility.snapshot();
expect(a11yTree).toMatchSnapshot();
});
test('full page (mobile)', async () => {
await page.emulate(puppeteer.devices['Pixel 2']);
const image = await page.screenshot({ fullPage: true });
expect(image).toMatchImageSnapshot();
});
test('no page errors', () => {
expect(pageErrors).toHaveLength(0);
});

Просмотреть файл

@ -0,0 +1,83 @@
/**
* @license
* Copyright 2021 The Go Authors. All rights reserved.
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/
import { Page } from 'puppeteer';
import './global-types';
import * as pg from './helpers/page';
import * as pkgsite from './helpers/pkgsite.page';
let page: Page;
beforeAll(async () => {
page = await pg.newPage();
await page.goto('/golang.org/x/pkgsite');
await pkgsite.prepare(page);
});
afterAll(async () => {
await page.close();
});
test('fixed header appears after scrolling', async () => {
await page.evaluate(() => window.scrollTo({ top: 250 }));
const image = await page.screenshot();
expect(image).toMatchImageSnapshot();
await page.evaluate(() => window.scrollTo({ top: 0 }));
});
describe('readme', () => {
test('expands', async () => {
await page.click(pg.select('readme-expand'));
await page.evaluate(() => window.scrollTo({ top: 0 }));
const expanded = await page.screenshot({ fullPage: true });
expect(expanded).toMatchImageSnapshot();
});
test('collapses', async () => {
await page.click(pg.select('readme-collapse'));
await page.evaluate(() => window.scrollTo({ top: 0 }));
const collapsed = await page.screenshot({ fullPage: true });
expect(collapsed).toMatchImageSnapshot();
});
});
describe('directories', () => {
test('expand', async () => {
await page.click(pg.select('directories-toggle'));
await page.evaluate(() => window.scrollTo({ top: 0 }));
const expanded = await page.screenshot({ fullPage: true });
expect(expanded).toMatchImageSnapshot();
});
test('collapse', async () => {
await page.click(pg.select('directories-toggle'));
await page.evaluate(() => window.scrollTo({ top: 0 }));
const collapsed = await page.screenshot({ fullPage: true });
expect(collapsed).toMatchImageSnapshot();
});
});
describe('jump to modal', () => {
test('opens', async () => {
await page.click(pg.select('jump-to-button'));
await page.evaluate(() => window.scrollTo({ top: 0 }));
const expanded = await page.screenshot();
expect(expanded).toMatchImageSnapshot();
});
test('closes', async () => {
await page.click(pg.select('close-dialog'));
await page.evaluate(() => window.scrollTo({ top: 0 }));
const collapsed = await page.screenshot();
expect(collapsed).toMatchImageSnapshot();
});
});
test('no page errors', () => {
expect(pageErrors).toHaveLength(0);
});

Просмотреть файл

@ -0,0 +1,35 @@
/**
* @license
* Copyright 2021 The Go Authors. All rights reserved.
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/
import puppeteer, { Page } from 'puppeteer';
import './global-types';
import * as pg from './helpers/page';
import * as pkgsite from './helpers/pkgsite.page';
let page: Page;
beforeAll(async () => {
page = await pg.newPage();
await page.emulate(puppeteer.devices['Pixel 2']);
await page.goto('/golang.org/x/pkgsite');
await pkgsite.prepare(page);
});
afterAll(async () => {
await page.close();
});
test('fixed header appears after scrolling', async () => {
await page.evaluate(() => window.scrollTo({ top: 250 }));
const image = await page.screenshot();
expect(image).toMatchImageSnapshot();
});
test('no page errors', () => {
expect(pageErrors).toHaveLength(0);
});

Просмотреть файл

@ -1,46 +0,0 @@
/**
* @license
* Copyright 2021 The Go Authors. All rights reserved.
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/
import './global-types';
import puppeteer, { Page } from 'puppeteer';
let page: Page;
beforeAll(async () => {
page = await newPage();
await page.goto(baseURL + '/search-help');
});
afterAll(async () => {
await page.close();
});
test('accessibility tree (desktop)', async () => {
const a11yTree = await page.accessibility.snapshot();
expect(a11yTree).toMatchSnapshot();
});
test('full page (desktop)', async () => {
const image = await page.screenshot({ fullPage: true });
expect(image).toMatchImageSnapshot();
});
test('accessibility tree (mobile)', async () => {
await page.emulate(puppeteer.devices['Pixel 2']);
const a11yTree = await page.accessibility.snapshot();
expect(a11yTree).toMatchSnapshot();
});
test('full page (mobile)', async () => {
await page.emulate(puppeteer.devices['Pixel 2']);
const image = await page.screenshot({ fullPage: true });
expect(image).toMatchImageSnapshot();
});
test('no page errors', () => {
expect(pageErrors).toHaveLength(0);
});

Просмотреть файл

@ -1,84 +0,0 @@
/**
* @license
* Copyright 2021 The Go Authors. All rights reserved.
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/
import './global-types';
import puppeteer, { Page } from 'puppeteer';
let page: Page;
beforeAll(async () => {
page = await newPage();
await page.goto(baseURL + '/search?q=http');
});
afterAll(async () => {
await page.close();
});
test('accessibility tree (desktop)', async () => {
await prepare(page);
const a11yTree = await page.accessibility.snapshot();
expect(a11yTree).toMatchSnapshot();
});
test('full page (desktop)', async () => {
await prepare(page);
const image = await page.screenshot({ fullPage: true });
expect(image).toMatchImageSnapshot();
});
test('accessibility tree (mobile)', async () => {
await prepare(page);
const a11yTree = await page.accessibility.snapshot();
expect(a11yTree).toMatchSnapshot();
});
test('full page (mobile)', async () => {
await page.emulate(puppeteer.devices['Pixel 2']);
await prepare(page);
const image = await page.screenshot({ fullPage: true });
expect(image).toMatchImageSnapshot();
});
test('no page errors', () => {
expect(pageErrors).toHaveLength(0);
});
/**
* prepare gets the page ready for snapshot tests by rewriting highly
* variable page content to constant values.
* @param page The page to prepare
*/
async function prepare(page: Page): Promise<void> {
await Promise.all([
page.$$eval('[data-test-id="snippet-title"]', els =>
els.map(el => {
el.innerHTML = 'net/http/pprof';
(el as HTMLAnchorElement).href = 'net/http/pprof';
})
),
page.$$eval('[data-test-id="snippet-synopsis"]', els =>
els.map(el => {
el.innerHTML =
'Package pprof serves via its HTTP server runtime profiling ' +
'data in the format expected by the pprof visualization tool.';
})
),
page.$$eval('[data-test-id="snippet-version"]', els =>
els.map(el => (el.innerHTML = 'go1.16.3'))
),
page.$$eval('[data-test-id="snippet-published"]', els =>
els.map(el => (el.innerHTML = 'Apr 1, 2021'))
),
page.$$eval('[data-test-id="snippet-importedby"]', els =>
els.map(el => (el.innerHTML = '11632'))
),
page.$$eval('[data-test-id="snippet-license"]', els =>
els.map(el => (el.innerHTML = 'BSD-3-Clause'))
),
]);
}

Просмотреть файл

@ -5,7 +5,12 @@
* license that can be found in the LICENSE file.
*/
import { toMatchImageSnapshot } from 'jest-image-snapshot';
import { configureToMatchImageSnapshot } from 'jest-image-snapshot';
// Extends jest to compare image snapshots.
const toMatchImageSnapshot = configureToMatchImageSnapshot({
customSnapshotIdentifier: ({ defaultIdentifier, counter }) => {
return defaultIdentifier.replace('test-ts', '').replace(`-${counter}`, '');
},
});
expect.extend({ toMatchImageSnapshot });

Просмотреть файл

@ -8,50 +8,30 @@
const puppeteer = require('puppeteer');
const NodeEnvironment = require('jest-environment-node');
const {
AUTHORIZATION = null,
BASE_URL = 'http://host.docker.internal:8080',
// GO_DISCOVERY_E2E_TEST_PORT default value should match ./global-setup.ts.
GO_DISCOVERY_E2E_TEST_PORT = 3000,
} = process.env;
// GO_DISCOVERY_E2E_TEST_PORT default value should match ./global-setup.ts.
const port = Number(process.env.GO_DISCOVERY_E2E_TEST_PORT ?? 3000);
/**
* PuppeteerEnvironment is a custom jest test environment. It extends the node
* test environment to initialize global variables, connect puppeteer on
* the host machine to the chromium instance running in docker, and add
* authorization to requests when the AUTHORIZATION env var is set.
* the host machine to the chromium instance.
*/
class PuppeteerEnvironment extends NodeEnvironment {
constructor(config) {
super(config);
this.global.baseURL = BASE_URL;
this.global.pageErrors = [];
this.global.newPage = async () => {
const page = await this.global.browser.newPage();
if (AUTHORIZATION) {
await page.setRequestInterception(true);
page.on('request', r => {
const url = new URL(r.url());
let headers = r.headers();
if (url.origin === BASE_URL) {
headers = { ...r.headers(), Authorization: `Bearer ${AUTHORIZATION}` };
}
r.continue({ headers });
});
}
page.on('pageerror', err => {
this.global.pageErrors.push(err);
});
return page;
};
}
async setup() {
await super.setup();
try {
this.global.browser = await puppeteer.connect({
browserWSEndpoint: `ws://localhost:${GO_DISCOVERY_E2E_TEST_PORT}`,
browserWSEndpoint: `ws://localhost:${port}`,
defaultViewport: { height: 800, width: 1280 },
});
} catch (e) {
console.error(e);
}
}
async teardown() {

Просмотреть файл

@ -1,210 +0,0 @@
/**
* @license
* Copyright 2021 The Go Authors. All rights reserved.
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/
import './global-types';
import puppeteer, { Page } from 'puppeteer';
describe('pkgsite (desktop)', () => {
let page: Page;
beforeEach(async () => {
page = await newPage();
await page.goto(baseURL + '/golang.org/x/pkgsite');
await prepare(page);
});
afterEach(async () => {
await page.close();
});
test('accessibility tree', async () => {
const a11yTree = await page.accessibility.snapshot();
expect(a11yTree).toMatchSnapshot();
});
test('full page', async () => {
const image = await page.screenshot({ fullPage: true });
expect(image).toMatchImageSnapshot({});
});
test('fixed header appears after scrolling', async () => {
await page.evaluate(() => window.scrollTo({ top: 250 }));
const image = await page.screenshot();
expect(image).toMatchImageSnapshot();
});
test('readme expand and collapse', async () => {
await page.click(select('readme-expand'));
await page.evaluate(() => window.scrollTo({ top: 0 }));
const expanded = await page.screenshot({ fullPage: true });
expect(expanded).toMatchImageSnapshot();
await page.click(select('readme-collapse'));
await page.evaluate(() => window.scrollTo({ top: 0 }));
const collapsed = await page.screenshot({ fullPage: true });
expect(collapsed).toMatchImageSnapshot();
});
test('directories expand and collapse', async () => {
await page.click(select('directories-toggle'));
await page.evaluate(() => window.scrollTo({ top: 0 }));
const expanded = await page.screenshot({ fullPage: true });
expect(expanded).toMatchImageSnapshot();
await page.click(select('directories-toggle'));
await page.evaluate(() => window.scrollTo({ top: 0 }));
const collapsed = await page.screenshot({ fullPage: true });
expect(collapsed).toMatchImageSnapshot();
});
test('jump to without identifiers', async () => {
await page.click(select('jump-to-button'));
await page.evaluate(() => window.scrollTo({ top: 0 }));
const expanded = await page.screenshot();
expect(expanded).toMatchImageSnapshot();
await page.click(select('close-dialog'));
await page.evaluate(() => window.scrollTo({ top: 0 }));
const collapsed = await page.screenshot();
expect(collapsed).toMatchImageSnapshot();
});
});
describe('pkgsite (mobile)', () => {
let page: Page;
beforeAll(async () => {
page = await newPage();
await page.emulate(puppeteer.devices['Pixel 2']);
await page.goto(baseURL + '/golang.org/x/pkgsite');
await prepare(page);
});
afterAll(async () => {
await page.close();
});
test('accessibility tree', async () => {
const a11yTree = await page.accessibility.snapshot();
expect(a11yTree).toMatchSnapshot();
});
test('full page', async () => {
const image = await page.screenshot({ fullPage: true });
expect(image).toMatchImageSnapshot();
});
test('fixed header appears after scrolling', async () => {
await page.evaluate(() => window.scrollTo({ top: 250 }));
const image = await page.screenshot();
expect(image).toMatchImageSnapshot();
});
});
describe('derrors', () => {
let page: Page;
beforeAll(async () => {
page = await newPage();
await page.goto(baseURL + '/golang.org/x/pkgsite/internal/derrors');
await prepare(page);
});
afterAll(async () => {
await page.close();
});
test('accessibility tree', async () => {
const a11yTree = await page.accessibility.snapshot();
expect(a11yTree).toMatchSnapshot();
});
test('full page', async () => {
const image = await page.screenshot({ fullPage: true });
expect(image).toMatchImageSnapshot();
});
test.each`
href
${'#section-documentation'}
${'#pkg-overview'}
${'#pkg-index'}
${'#pkg-constants'}
${'#pkg-variables'}
${'#pkg-functions'}
${'#Add'}
${'#pkg-types'}
${'#StackError'}
${'#NewStackError'}
${'#section-sourcefiles'}
`('doc outline $href', async ({ href }) => {
await page.click(`[href="${href}"][role="treeitem"]`);
const image = await page.screenshot();
expect(image).toMatchImageSnapshot();
});
test('jump to with identifiers', async () => {
await page.click(select('jump-to-button'));
const expanded = await page.screenshot();
expect(expanded).toMatchImageSnapshot();
await page.keyboard.type('Wrap');
const inputWrap = await page.screenshot();
expect(inputWrap).toMatchImageSnapshot();
await page.keyboard.press('Enter');
const wrap = await page.screenshot();
expect(wrap).toMatchImageSnapshot();
});
});
test('no page errors', () => {
expect(pageErrors).toHaveLength(0);
});
/**
* select will create a data-test-id attribute selector for a given test id.
* @param testId the test id of the element to select.
* @param rest a place to add combinators and additional selectors.
* @returns an attribute selector.
*/
function select(testId: string, rest = ''): string {
return `[data-test-id="${testId}"] ${rest}`;
}
/**
* prepare gets the page ready for snapshot testing by rewriting highly
* variable page content to constant values.
* @param page The page to prepare
*/
async function prepare(page: Page): Promise<void> {
await Promise.all([
// Add styles to disable animation transitions and
// hide blinking curson in input boxes.
page.addStyleTag({
content: `
*,
*::after,
*::before {
transition-delay: 0s !important;
transition-duration: 0s !important;
animation-delay: -0.0001s !important;
animation-duration: 0s !important;
animation-play-state: paused !important;
caret-color: transparent;
}`,
}),
page.$eval(
select('UnitHeader-version', 'a'),
el =>
((el as HTMLElement).innerHTML =
'<span class="UnitHeader-detailItemSubtle">Version: </span>v0.0.0')
),
page.$eval(
select('UnitHeader-commitTime'),
el => ((el as HTMLElement).innerHTML = 'Published: Apr 16, 2021')
),
page.$$eval(select('UnitHeader-imports', 'a'), els =>
els.map(el => (el.innerHTML = 'Imports: 0'))
),
page.$$eval(select('UnitHeader-importedby', 'a'), els =>
els.map(el => (el.innerHTML = 'Imported by: 0'))
),
]);
}

Просмотреть файл

@ -1,8 +0,0 @@
/**
* @license
* Copyright 2021 The Go Authors. All rights reserved.
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/
test.todo('todo');

Просмотреть файл

@ -1,8 +0,0 @@
/**
* @license
* Copyright 2021 The Go Authors. All rights reserved.
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/
test.todo('todo');

Просмотреть файл

@ -1,8 +0,0 @@
/**
* @license
* Copyright 2021 The Go Authors. All rights reserved.
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/
test.todo('todo');

Просмотреть файл

@ -1,8 +0,0 @@
/**
* @license
* Copyright 2021 The Go Authors. All rights reserved.
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/
test.todo('todo');

42
e2e/unit.desktop.test.ts Normal file
Просмотреть файл

@ -0,0 +1,42 @@
/**
* @license
* Copyright 2021 The Go Authors. All rights reserved.
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/
import { Page } from 'puppeteer';
import './global-types';
import * as pg from './helpers/page';
import * as unit from './helpers/unit.page';
import { testcases } from './unit.testcases';
let page: Page;
beforeAll(async () => {
page = await pg.newPage();
await page.setViewport({ height: 2732, width: 1366 });
});
afterAll(async () => {
await page.close();
});
testcases('$name', async ({ path }) => {
await page.goto(path);
await prepare(page);
const image = await page.screenshot();
// eslint-disable-next-line jest/no-standalone-expect
expect(image).toMatchImageSnapshot();
});
test('no page errors', () => {
expect(pageErrors).toHaveLength(0);
});
async function prepare(page: Page): Promise<void> {
await unit.prepare(page);
return pg.$eval(page, '.Documentation-index', index => index.remove());
}

42
e2e/unit.mobile.test.ts Normal file
Просмотреть файл

@ -0,0 +1,42 @@
/**
* @license
* Copyright 2021 The Go Authors. All rights reserved.
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/
import { Page } from 'puppeteer';
import './global-types';
import * as pg from './helpers/page';
import * as unit from './helpers/unit.page';
import { testcases } from './unit.testcases';
let page: Page;
beforeAll(async () => {
page = await pg.newPage();
await page.setViewport({ height: 2732, width: 500 });
});
afterAll(async () => {
await page.close();
});
testcases('$name', async ({ path }) => {
await page.goto(path);
await prepare(page);
const image = await page.screenshot();
// eslint-disable-next-line jest/no-standalone-expect
expect(image).toMatchImageSnapshot();
});
test('no page errors', () => {
expect(pageErrors).toHaveLength(0);
});
async function prepare(page: Page): Promise<void> {
await unit.prepare(page);
await pg.$eval(page, '.Documentation-index', el => el.remove());
}

33
e2e/unit.testcases.ts Normal file
Просмотреть файл

@ -0,0 +1,33 @@
/**
* @license
* Copyright 2021 The Go Authors. All rights reserved.
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/
export const testcases = test.each`
name | path
${'standard library'} | ${'/std@go1.16.3'}
${'standard library versions'} | ${'/std?tab=versions'}
${'standard library licenses'} | ${'/std@go1.16.3?tab=licenses'}
${'errors versions'} | ${'/errors?tab=versions'}
${'errors licenses'} | ${'/errors@go1.16.3?tab=licenses'}
${'errors imports'} | ${'/errors@go1.16.3?tab=imports'}
${'tools'} | ${'/golang.org/x/tools@v0.1.1'}
${'tools licenses'} | ${'/golang.org/x/tools@v0.1.1?tab=licenses'}
${'tools imports'} | ${'/golang.org/x/tools?tab=versions'}
${'module that is not a package'} | ${'/golang.org/x/tools@v0.1.1'}
${'module that is also a package'} | ${'/gocloud.dev@v0.22.0'}
${'really long import path'} | ${'/github.com/envoyproxy/go-control-plane@v0.9.8/envoy/config/filter/network/http_connection_manager/v2'}
${'no documentation'} | ${'/golang.org/x/build@v0.0.0-20210512165013-0536f7398f0e/cmd/gopherstats'}
${'package with multiple licenses'} | ${'/github.com/apache/thrift@v0.14.1?tab=licenses'}
${'package that exists in multiple modules at the same version'} | ${'/github.com/hashicorp/vault/api@v1.0.3'}
${'package with multiple nested modules'} | ${'/github.com/Azure/go-autorest/autorest@v0.11.18#section-directories'}
${'package not at latest version of module'} | ${'/golang.org/x/tools/cmd/vet'}
${'package with higher major version'} | ${'/mvdan.cc/sh/syntax'}
${'page that will redirect'} | ${'/github.com/jackc/pgx/pgxpool'}
${'package with multi-GOOS'} | ${'/github.com/creack/pty@v1.1.11'}
${'retracted package'} | ${'/k8s.io/client-go@v1.5.2'}
${'deprecated package'} | ${'/github.com/jba/bit'}
${'package with deprecated symbols'} | ${'/cuelang.org/go@v0.3.2/cue'}
`;