diff --git a/.eslintrc.yaml b/.eslintrc.yaml index d7a122bc..d43cb1f5 100644 --- a/.eslintrc.yaml +++ b/.eslintrc.yaml @@ -13,3 +13,5 @@ overrides: - files: - "*.test.ts" extends: plugin:jest/recommended + rules: + jest/no-standalone-expect: off diff --git a/e2e/badge.test.ts b/e2e/badge.test.ts deleted file mode 100644 index 3fd71975..00000000 --- a/e2e/badge.test.ts +++ /dev/null @@ -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); -}); diff --git a/e2e/basic.desktop.test.ts b/e2e/basic.desktop.test.ts new file mode 100644 index 00000000..d451781c --- /dev/null +++ b/e2e/basic.desktop.test.ts @@ -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); +}); diff --git a/e2e/basic.mobile.test.ts b/e2e/basic.mobile.test.ts new file mode 100644 index 00000000..e2c12200 --- /dev/null +++ b/e2e/basic.mobile.test.ts @@ -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); +}); diff --git a/e2e/basic.testcases.ts b/e2e/basic.testcases.ts new file mode 100644 index 00000000..90bacb53 --- /dev/null +++ b/e2e/basic.testcases.ts @@ -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} +`; diff --git a/e2e/error.test.ts b/e2e/error.test.ts deleted file mode 100644 index 47cb8c5b..00000000 --- a/e2e/error.test.ts +++ /dev/null @@ -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); -}); diff --git a/e2e/errors.desktop.test.ts b/e2e/errors.desktop.test.ts new file mode 100644 index 00000000..589bf852 --- /dev/null +++ b/e2e/errors.desktop.test.ts @@ -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); +}); diff --git a/e2e/fetch.test.ts b/e2e/fetch.test.ts deleted file mode 100644 index fa712968..00000000 --- a/e2e/fetch.test.ts +++ /dev/null @@ -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); -}); diff --git a/e2e/global-types.ts b/e2e/global-types.ts index 5b992bc1..43cd2898 100644 --- a/e2e/global-types.ts +++ b/e2e/global-types.ts @@ -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; - - /** - * 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[]; } diff --git a/e2e/helpers/page.ts b/e2e/helpers/page.ts new file mode 100644 index 00000000..49a7c9f9 --- /dev/null +++ b/e2e/helpers/page.ts @@ -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 { + 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 { + 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 { + 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 { + if (await page.$(selector)) { + await page.$$eval(selector, cb); + } +} diff --git a/e2e/helpers/pkgsite.page.ts b/e2e/helpers/pkgsite.page.ts new file mode 100644 index 00000000..cf7d1905 --- /dev/null +++ b/e2e/helpers/pkgsite.page.ts @@ -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 { + await unit.prepare(page); + await Promise.all([ + pg.$eval( + page, + pg.select('UnitHeader-version', 'a'), + el => + ((el as HTMLElement).innerHTML = + 'Version: 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')) + ), + ]); +} diff --git a/e2e/helpers/search.page.ts b/e2e/helpers/search.page.ts new file mode 100644 index 00000000..c11b8701 --- /dev/null +++ b/e2e/helpers/search.page.ts @@ -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 { + 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')) + ), + ]); +} diff --git a/e2e/helpers/unit.page.ts b/e2e/helpers/unit.page.ts new file mode 100644 index 00000000..c4992b18 --- /dev/null +++ b/e2e/helpers/unit.page.ts @@ -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 { + 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')), + ]); +} diff --git a/e2e/homepage.test.ts b/e2e/homepage.test.ts deleted file mode 100644 index a049deae..00000000 --- a/e2e/homepage.test.ts +++ /dev/null @@ -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); -}); diff --git a/e2e/license-policy.test.ts b/e2e/license-policy.test.ts deleted file mode 100644 index 401b2ee9..00000000 --- a/e2e/license-policy.test.ts +++ /dev/null @@ -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); -}); diff --git a/e2e/pkgsite.desktop.test.ts b/e2e/pkgsite.desktop.test.ts new file mode 100644 index 00000000..0dc5deab --- /dev/null +++ b/e2e/pkgsite.desktop.test.ts @@ -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); +}); diff --git a/e2e/pkgsite.mobile.test.ts b/e2e/pkgsite.mobile.test.ts new file mode 100644 index 00000000..f596502e --- /dev/null +++ b/e2e/pkgsite.mobile.test.ts @@ -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); +}); diff --git a/e2e/search-help.test.ts b/e2e/search-help.test.ts deleted file mode 100644 index 689b89a4..00000000 --- a/e2e/search-help.test.ts +++ /dev/null @@ -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); -}); diff --git a/e2e/search.test.ts b/e2e/search.test.ts deleted file mode 100644 index 02b4fbbb..00000000 --- a/e2e/search.test.ts +++ /dev/null @@ -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 { - 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')) - ), - ]); -} diff --git a/e2e/setup.ts b/e2e/setup.ts index b4bd61ce..20fe36f8 100644 --- a/e2e/setup.ts +++ b/e2e/setup.ts @@ -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 }); diff --git a/e2e/test-environment.js b/e2e/test-environment.js index e56528f1..659d95be 100644 --- a/e2e/test-environment.js +++ b/e2e/test-environment.js @@ -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(); - this.global.browser = await puppeteer.connect({ - browserWSEndpoint: `ws://localhost:${GO_DISCOVERY_E2E_TEST_PORT}`, - defaultViewport: { height: 800, width: 1280 }, - }); + try { + this.global.browser = await puppeteer.connect({ + browserWSEndpoint: `ws://localhost:${port}`, + defaultViewport: { height: 800, width: 1280 }, + }); + } catch (e) { + console.error(e); + } } async teardown() { diff --git a/e2e/unit-details.test.ts b/e2e/unit-details.test.ts deleted file mode 100644 index ac992edd..00000000 --- a/e2e/unit-details.test.ts +++ /dev/null @@ -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 { - 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 = - 'Version: 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')) - ), - ]); -} diff --git a/e2e/unit-importedby.test.ts b/e2e/unit-importedby.test.ts deleted file mode 100644 index 5f19dfaa..00000000 --- a/e2e/unit-importedby.test.ts +++ /dev/null @@ -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'); diff --git a/e2e/unit-imports.test.ts b/e2e/unit-imports.test.ts deleted file mode 100644 index 5f19dfaa..00000000 --- a/e2e/unit-imports.test.ts +++ /dev/null @@ -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'); diff --git a/e2e/unit-licenses.test.ts b/e2e/unit-licenses.test.ts deleted file mode 100644 index 5f19dfaa..00000000 --- a/e2e/unit-licenses.test.ts +++ /dev/null @@ -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'); diff --git a/e2e/unit-versions.test.ts b/e2e/unit-versions.test.ts deleted file mode 100644 index 5f19dfaa..00000000 --- a/e2e/unit-versions.test.ts +++ /dev/null @@ -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'); diff --git a/e2e/unit.desktop.test.ts b/e2e/unit.desktop.test.ts new file mode 100644 index 00000000..139f0fe5 --- /dev/null +++ b/e2e/unit.desktop.test.ts @@ -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 { + await unit.prepare(page); + return pg.$eval(page, '.Documentation-index', index => index.remove()); +} diff --git a/e2e/unit.mobile.test.ts b/e2e/unit.mobile.test.ts new file mode 100644 index 00000000..e8caea01 --- /dev/null +++ b/e2e/unit.mobile.test.ts @@ -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 { + await unit.prepare(page); + await pg.$eval(page, '.Documentation-index', el => el.remove()); +} diff --git a/e2e/unit.testcases.ts b/e2e/unit.testcases.ts new file mode 100644 index 00000000..6c20bb86 --- /dev/null +++ b/e2e/unit.testcases.ts @@ -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'} +`;