bext: cover 'Search on Sourcegraph' buttons on GitHub search pages with integration tests (#31572)

* Add GitHub search pages enhancements integration tests

* Configure git-lfs for *.har files (#31624)
This commit is contained in:
Taras Yemets 2022-03-08 14:50:25 +02:00 committed by GitHub
parent d76bbb0a74
commit 0e71b5ef02
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 397 additions and 90589 deletions

View File

@ -3,7 +3,7 @@
# shellcheck disable=SC1090
source "$HOME/.profile"
# Fetch the latest origin/master to accurately determine the set of changed
# Fetch the latest origin/main to accurately determine the set of changed
# files on this branch.
echo "Running git fetch..."
git fetch

1
.gitattributes vendored
View File

@ -4,3 +4,4 @@ cmd/repo-updater/repos/testdata/** linguist-generated=true
**/*.pb.go linguist-generated=true
CHANGELOG.md merge=union
**/mock_*_test.go linguist-generated=true
*.har filter=lfs diff=lfs merge=lfs -text

View File

@ -140,7 +140,17 @@ Click reload for Sourcegraph at `about:debugging`
## Testing
- Unit tests: `sg test bext`
- Integration tests: `sg test bext-build` & `sg test bext-integration`
- Integration tests
- install [git lfs](https://git-lfs.github.com) if it's not installed
- run
- fetch snapshots with `git lfs fetch`
- build browser extension with `sg test bext-build`
- run tests with `sg test bext-integration`
- develop
- add/edit test case or at least its part with navigation to a certain page
- if there's no page snapshot for created test or page URL referenced in the existing test has been changed, test will fail with 'Page not found' error
- to generate or update page snapshots for tests run `yarn record-integration`
- after pushing the updated snapshots to remote they will be stored on GitHub LFS and snapshot files will contain references to that storage
- E2E tests: `sg test bext-build` & `sg test bext-e2e`
### E2E tests

View File

@ -1,7 +1,35 @@
const { readdir, readFile } = require('mz/fs')
const shelljs = require('shelljs')
const recordSnapshot = grepValue =>
shelljs.exec(
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
`POLLYJS_MODE=record SOURCEGRAPH_BASE_URL=https://sourcegraph.com yarn test-integration --grep='${grepValue}'`,
(error, stdout, stderr) => {
if (error) {
console.error(error)
return
}
console.log(`stdout: ${stdout}`)
console.error(`stderr: ${stderr}`)
}
)
;(async () => {
// 1. Record by --grep args
const args = process.argv.slice(2)
for (let index = 0; index < args.length; ++index) {
if (args[index] === '--grep' && !!args[index + 1]) {
recordSnapshot(args[index + 1])
return
}
if (args[index].startsWith('--grep=')) {
recordSnapshot(args.replace('--grep=', ''))
return
}
}
// 2. Record all tests
const fileNames = await readdir('./src/integration')
const testFileNames = fileNames.filter(fileName => fileName.endsWith('.test.ts'))
const testFiles = await Promise.all(
@ -16,17 +44,7 @@ const shelljs = require('shelljs')
.map(matchArray => matchArray[2])
for (const testName of testNames) {
shelljs.exec(
`POLLYJS_MODE=record SOURCEGRAPH_BASE_URL=https://sourcegraph.com yarn test-integration --grep='${testName}'`,
(error, stdout, stderr) => {
if (error) {
console.error(error)
return
}
console.log(`stdout: ${stdout}`)
console.error(`stderr: ${stderr}`)
}
)
recordSnapshot(testName)
}
})().catch(error => {
console.log(error)

View File

@ -14,37 +14,30 @@ import { closeInstallPageTab } from './shared'
describe('GitHub', () => {
let driver: Driver
before(async () => {
let testContext: BrowserIntegrationTestContext
const mockUrls = (urls: string[]) => {
for (const url of urls) {
testContext.server.any(url).intercept((request, response) => {
response.sendStatus(200)
})
}
}
beforeEach(async function () {
driver = await createDriverForTest({ loadExtension: true })
await closeInstallPageTab(driver.browser)
if (driver.sourcegraphBaseUrl !== 'https://sourcegraph.com') {
await driver.setExtensionSourcegraphUrl()
}
})
after(() => driver?.close())
let testContext: BrowserIntegrationTestContext
beforeEach(async function () {
testContext = await createBrowserIntegrationTestContext({
driver,
currentTest: this.currentTest!,
directory: __dirname,
})
const URLS_TO_MOCK = [
'https://collector.github.com/*path',
'https://api.github.com/_private/browser/*',
'https://github.com/*path/find-definition',
'https://github.com/gorilla/mux/commits/checks-statuses-rollups',
'https://github.com/commits/badges',
]
// Requests to other origins that we need to ignore to prevent breaking tests.
for (const urlToMock of URLS_TO_MOCK) {
testContext.server.any(urlToMock).intercept((request, response) => {
response.sendStatus(200)
})
}
mockUrls(['https://api.github.com/_private/browser/*', 'https://collector.github.com/*path'])
testContext.server.any('https://api.github.com/repos/*').intercept((request, response) => {
response
@ -101,7 +94,10 @@ describe('GitHub', () => {
await driver.page.emulateMediaFeatures([{ name: 'prefers-color-scheme', value: 'light' }])
})
afterEachSaveScreenshotIfFailed(() => driver.page)
afterEach(() => testContext?.dispose())
afterEach(async () => {
await testContext?.dispose()
await driver?.close()
})
it('adds "View on Sourcegraph" buttons to files', async () => {
const repoName = 'github.com/sourcegraph/jsonrpc2'
@ -136,7 +132,9 @@ describe('GitHub', () => {
})
})
it("shows hover tooltips when hovering a token and respects 'Enable single click to go to definition' setting", async () => {
it('shows hover tooltips when hovering a token and respects "Enable single click to go to definition" setting', async () => {
mockUrls(['https://github.com/*path/find-definition'])
const { mockExtension, Extensions, extensionSettings } = setupExtensionMocking({
pollyServer: testContext.server,
sourcegraphBaseUrl: driver.sourcegraphBaseUrl,
@ -304,6 +302,8 @@ describe('GitHub', () => {
// For each pull request test, set up a mock extension that verifies that the correct
// file and revision info reach extensions.
beforeEach(() => {
mockUrls(['https://github.com/*path/find-definition'])
const { mockExtension, Extensions, extensionSettings } = setupExtensionMocking({
pollyServer: testContext.server,
sourcegraphBaseUrl: driver.sourcegraphBaseUrl,
@ -623,6 +623,12 @@ describe('GitHub', () => {
describe('Commit view', () => {
beforeEach(() => {
mockUrls([
'https://github.com/*path/find-definition',
'https://github.com/**/commits/checks-statuses-rollups',
'https://github.com/commits/badges',
])
const { mockExtension, Extensions, extensionSettings } = setupExtensionMocking({
pollyServer: testContext.server,
sourcegraphBaseUrl: driver.sourcegraphBaseUrl,
@ -736,4 +742,284 @@ describe('GitHub', () => {
})
})
})
describe('Search pages', () => {
const sourcegraphSearchPage = 'https://sourcegraph.com/search'
describe('Simple and advanced search pages', () => {
beforeEach(() => {
mockUrls(['https://github.githubassets.com/favicons/*path'])
})
const pages = ['https://github.com/search', 'https://github.com/search/advanced']
it('render "Search on Sourcegraph" button', async () => {
for (const page of pages) {
await driver.newPage()
await driver.page.goto(page)
const linkToSourcegraph = await driver.page.waitForSelector(
'[data-testid="search-on-sourcegraph"]',
{ timeout: 3000 }
)
assert(linkToSourcegraph, 'Expected link to Sourcegraph search page exists')
}
})
it('if search input is empty "Search on Sourcegraph" click navigates to Sourcegraph search page with type "repo" and empty search query', async () => {
for (const page of pages) {
await driver.newPage()
await driver.page.goto(page)
const linkToSourcegraph = await driver.page.waitForSelector(
'[data-testid="search-on-sourcegraph"]',
{ timeout: 3000 }
)
let hasRedirectedToSourcegraphSearch = false
testContext.server.get(sourcegraphSearchPage).intercept(request => {
if (request.query.q === 'type:repo') {
hasRedirectedToSourcegraphSearch = true
}
})
await linkToSourcegraph?.click()
await driver.page.waitForTimeout(3000)
assert(
hasRedirectedToSourcegraphSearch,
'Expected to be redirected to Sourcegraph search page with type "repo" and empty query'
)
}
})
it('if search input has value "Search on Sourcegraph" click navigates to Sourcegraph search page with type "repo" and search query', async () => {
for (const page of pages) {
await driver.newPage()
await driver.page.goto(page)
const query = 'Hello world!'
const searchInput = await driver.page.waitForSelector('#search_form input[type="text"]')
const linkToSourcegraph = await driver.page.waitForSelector(
'[data-testid="search-on-sourcegraph"]',
{ timeout: 3000 }
)
let hasRedirectedToSourcegraphSearch = false
testContext.server.get(sourcegraphSearchPage).intercept(request => {
if (['type:repo', query].every(value => request.query.q?.includes(value))) {
hasRedirectedToSourcegraphSearch = true
}
})
await searchInput?.type(query, { delay: 100 })
await linkToSourcegraph?.click()
await driver.page.waitForTimeout(1000)
assert(
hasRedirectedToSourcegraphSearch,
'Expected to be redirected to Sourcegraph search page with type "repo" and search query'
)
}
})
})
// global and repository search pages
describe('Search results page', () => {
beforeEach(() => {
mockUrls([
'https://github.githubassets.com/favicons/*path',
'https://github.com/_graphql/GetSuggestedNavigationDestinations',
'https://github.com/**/commits/checks-statuses-rollups',
'https://github.com/commits/badges',
])
})
const buildGitHubSearchResultsURL = (page: string, searchTerm: string): string => {
const url = new URL(page)
url.searchParams.set('q', searchTerm)
return url.toString()
}
const globalSearchPage = 'https://github.com/search'
const repo = 'sourcegraph/sourcegraph'
const repoSearchPage = `https://github.com/${repo}/search`
const pages = [
{
url: globalSearchPage,
htmlResultLangSelector: 'ul.filter-list li:nth-child(1) a.filter-item',
commitResultTypeSelector: 'nav.menu .menu-item:nth-child(3)',
},
{
url: repoSearchPage,
htmlResultLangSelector: 'ul.filter-list li:nth-child(2) a.filter-item',
commitResultTypeSelector: 'nav.menu .menu-item:nth-child(2)',
},
]
const viewportM = { width: 768, height: 1024 }
const viewportL = { width: 1024, height: 768 }
const viewportConfigs = [
{
viewport: viewportM,
sourcegraphButtonSelector: '#pageSearchFormSourcegraphButton [data-testid="search-on-sourcegraph"]',
searchInputSelector: ".application-main form.js-site-search-form input.form-control[name='q']",
},
{
viewport: viewportL,
searchInputSelector: "header form.js-site-search-form input.form-control[name='q']",
sourcegraphButtonSelector:
'#headerSearchInputSourcegraphButton [data-testid="search-on-sourcegraph"]',
},
]
it('renders "Search on Sourcegraph" button', async () => {
for (const page of pages) {
const url = buildGitHubSearchResultsURL(page.url, 'hello')
for (const { sourcegraphButtonSelector } of viewportConfigs) {
await driver.page.goto(url)
const linkToSourcegraph = await driver.page.waitForSelector(sourcegraphButtonSelector, {
timeout: 3000,
})
assert(linkToSourcegraph, 'Expected link to Sourcegraph search page exists')
}
}
})
it('"Search on Sourcegraph" click navigates to Sourcegraph search page with proper type and query', async () => {
const searchTerm = 'hello'
for (const page of pages) {
const url = buildGitHubSearchResultsURL(page.url, searchTerm)
for (const { viewport, sourcegraphButtonSelector } of viewportConfigs) {
await driver.newPage()
await driver.page.goto(url)
await driver.page.setViewport(viewport)
const linkToSourcegraph = await driver.page.waitForSelector(sourcegraphButtonSelector, {
timeout: 3000,
})
let hasRedirectedToSourcegraphSearch = false
testContext.server.get(sourcegraphSearchPage).intercept(request => {
if (page.url === globalSearchPage) {
hasRedirectedToSourcegraphSearch = ['type:repo', searchTerm].every(value =>
request.query.q?.includes(value)
)
} else if (page.url === repoSearchPage) {
hasRedirectedToSourcegraphSearch = [`repo:${repo}`, searchTerm].every(value =>
request.query.q?.includes(value)
)
}
})
await linkToSourcegraph?.click()
await driver.page.waitForTimeout(3000)
assert(
hasRedirectedToSourcegraphSearch,
'Expected to be redirected to Sourcegraph search page with proper result type and query'
)
}
}
})
it('"Search on Sourcegraph" click navigates to Sourcegraph search page with proper type and search query from search input', async () => {
const initialQuery = 'hello'
for (const page of pages) {
const url = buildGitHubSearchResultsURL(page.url, initialQuery)
const query = 'world'
for (const { viewport, sourcegraphButtonSelector, searchInputSelector } of viewportConfigs) {
await driver.newPage()
await driver.page.goto(url.toString())
await driver.page.setViewport(viewport)
const searchInput = await driver.page.waitForSelector(searchInputSelector)
const linkToSourcegraph = await driver.page.waitForSelector(sourcegraphButtonSelector, {
timeout: 3000,
})
let hasRedirectedToSourcegraphSearch = false
testContext.server.get(sourcegraphSearchPage).intercept(request => {
const resultQuery = `${initialQuery} ${query}`
if (page.url === globalSearchPage) {
hasRedirectedToSourcegraphSearch = ['type:repo', resultQuery].every(value =>
request.query.q?.includes(value)
)
} else if (page.url === repoSearchPage) {
hasRedirectedToSourcegraphSearch = [`repo:${repo}`, resultQuery].every(value =>
request.query.q?.includes(value)
)
}
})
// For some reason puppeteer when typing into input field prepends the exising value.
// To replicate the natural behavior we navigate to the end of exisiting value and then start typing.
await searchInput?.focus()
for (const _char of initialQuery) {
await driver.page.keyboard.press('ArrowRight')
}
await searchInput?.type(` ${query}`, { delay: 100 })
await driver.page.keyboard.press('Escape') // if input focus opened dropdown, ensure the latter is closed
await linkToSourcegraph?.click()
await driver.page.waitForTimeout(1000)
assert(
hasRedirectedToSourcegraphSearch,
'Expected to be redirected to Sourcegraph search page with type "repo" and input search query'
)
}
}
})
it('"Search on Sourcegraph" click navigates to Sourcegraph search page with proper result type and language', async () => {
const searchTerm = 'hello'
for (const page of pages) {
const url = buildGitHubSearchResultsURL(page.url, searchTerm)
await driver.newPage()
await driver.page.goto(url)
await driver.page.setViewport(viewportL)
let hasRedirectedToSourcegraphSearch = false
testContext.server.get(sourcegraphSearchPage).intercept(request => {
if (['type:commit', 'lang:HTML', searchTerm].every(value => request.query.q?.includes(value))) {
hasRedirectedToSourcegraphSearch = true
}
})
// filter results by language (handled by client-side routing)
const htmlButton = await driver.page.waitForSelector(page.htmlResultLangSelector)
await htmlButton?.click()
await driver.page.waitForTimeout(3000)
// filter results by type (handled by client-side routing)
const commitsButton = await driver.page.waitForSelector(page.commitResultTypeSelector)
commitsButton?.click()
await driver.page.waitForTimeout(3000)
const linkToSourcegraph = await driver.page.waitForSelector(
'#headerSearchInputSourcegraphButton [data-testid="search-on-sourcegraph"]',
{ timeout: 3000 }
)
await linkToSourcegraph?.click()
await driver.page.waitForTimeout(3000)
assert(
hasRedirectedToSourcegraphSearch,
'Expected to be redirected to Sourcegraph search page with type "commit", language "HTML" and search query'
)
}
})
})
})
})

View File

@ -469,7 +469,7 @@ const getSourcegraphResultType = (): SourcegraphResultType | '' => {
case 'code':
return ''
default:
return isSimpleSearchPage() ? 'repo' : ''
return isSimpleSearchPage() || isAdvancedSearchPage() ? 'repo' : ''
}
}
@ -553,6 +553,7 @@ function enhanceSearchPage(sourcegraphURL: string, mutations: Observable<Mutatio
className={classNames('btn', 'm-auto', className)}
iconClassName={classNames('mr-1', 'v-align-middle', styles.icon)}
href={sourcegraphSearchURL.href}
dataTestId="search-on-sourcegraph"
onClick={event => {
const searchQuery = buildSourcegraphQuery(getSearchQuery())

View File

@ -225,6 +225,7 @@ func addBrowserExt(pipeline *bk.Pipeline) {
bk.Env("LOG_BROWSER_CONSOLE", "true"),
bk.Env("SOURCEGRAPH_BASE_URL", "https://sourcegraph.com"),
bk.Env("POLLYJS_MODE", "replay"), // ensure that we use existing recordings
bk.Cmd("git-lfs fetch"),
bk.Cmd("yarn --frozen-lockfile --network-timeout 60000"),
bk.Cmd("yarn --cwd client/browser -s run build"),
bk.Cmd("yarn run cover-browser-integration"),