bazel: add integration test target (#49279)

Adds client integration tests target, got it running locally and
disabled it for CI until the $DISPLAY issue is resolved.

## Test plan

1. `bazel test //client/web/src/integration:integration-tests`
2. [The CI build with Bazel
checks](https://buildkite.com/sourcegraph/sourcegraph/builds/210860) 
This commit is contained in:
Valery Bugakov 2023-03-30 06:01:09 -07:00 committed by GitHub
parent 9e0902a497
commit 627f5a6758
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 82 additions and 23 deletions

View File

@ -23,7 +23,10 @@ export const ROOT_PATH = IS_BAZEL ? process.cwd() : resolveWithSymlink(__dirname
export const WORKSPACES_PATH = resolveWithSymlink(ROOT_PATH, 'client')
export const NODE_MODULES_PATH = resolveWithSymlink(ROOT_PATH, 'node_modules')
export const MONACO_EDITOR_PATH = resolveWithSymlink(NODE_MODULES_PATH, 'monaco-editor')
export const STATIC_ASSETS_PATH = resolveWithSymlink(ROOT_PATH, 'ui/assets')
export const STATIC_ASSETS_PATH = resolveWithSymlink(
ROOT_PATH,
IS_BAZEL && process.env.WEB_BUNDLE_PATH ? process.env.WEB_BUNDLE_PATH : 'ui/assets'
)
function getWorkspaceNodeModulesPaths(): string[] {
const workspaces = fs.readdirSync(WORKSPACES_PATH)

View File

@ -347,7 +347,6 @@ ts_project(
"//:node_modules/@types/node",
"//:node_modules/@types/puppeteer",
"//:node_modules/@types/react",
"//:node_modules/axe-core", #keep
"//:node_modules/classnames",
"//:node_modules/comlink",
"//:node_modules/core-js",

View File

@ -9,7 +9,7 @@
* https://github.com/davidNHK/pollyjs/blob/3e876a8cc0b28e8ef422763762bdab10027bb25d/packages/%40pollyjs/core/src/-private/logger.js
*
*/
if (process.env.CI) {
if (process.env.CI || process.env.LOG_BROWSER_CONSOLE === 'false') {
const originalLog = console.log
const originalError = console.error
const originalGroup = console.group

View File

@ -80,6 +80,7 @@ ts_project(
"//:node_modules/@types/react",
"//:node_modules/@types/sinon",
"//:node_modules/@types/uuid",
"//:node_modules/axe-core", #keep
"//:node_modules/chalk",
"//:node_modules/date-fns",
"//:node_modules/delay",

View File

@ -47,10 +47,10 @@ export const oncePageEvent = <E extends keyof PageEventObject>(page: Page, event
export const extractStyles = (page: puppeteer.Page): Promise<string> =>
page.evaluate(() =>
[...document.styleSheets].reduce(
Array.from(document.styleSheets).reduce(
(styleSheetRules, styleSheet) =>
styleSheetRules.concat(
[...styleSheet.cssRules].reduce((rules, rule) => rules.concat(rule.cssText), '')
Array.from(styleSheet.cssRules).reduce((rules, rule) => rules.concat(rule.cssText), '')
),
''
)
@ -147,9 +147,18 @@ function findElementRegexpStrings(
}
function findElementMatchingRegexps(tag: string, regexps: string[]): HTMLElement | null {
for (const regexpString of regexps) {
const regexp = new RegExp(regexpString)
for (const element of document.querySelectorAll<HTMLElement>(tag)) {
// This method is invoked via puppeteer.Page.eval* and runs in the browser context.
// This method must not use anything outside its own scope such as variables or functions,
// including babel helpers from transpilation. Therefore this method must be written in
// legacy-compatible JavaScript.
const elements = document.querySelectorAll<HTMLElement>(tag)
// eslint-disable-next-line @typescript-eslint/prefer-for-of
for (let regexI = 0; regexI < regexps.length; regexI++) {
const regexp = new RegExp(regexps[regexI])
// eslint-disable-next-line @typescript-eslint/prefer-for-of
for (let elementI = 0; elementI < elements.length; elementI++) {
const element = elements[elementI]
if (!element.offsetParent) {
// Ignore hidden elements
continue
@ -509,9 +518,7 @@ export class Driver {
}
public async paste(value: string): Promise<void> {
await this.page.evaluate(async (value: string) => {
await navigator.clipboard.writeText(value)
}, value)
await this.page.evaluate((value: string) => navigator.clipboard.writeText(value), value)
const modifier = os.platform() === 'darwin' ? Key.Meta : Key.Control
await this.page.keyboard.down(modifier)
await this.page.keyboard.press('v')

11
client/web/BUILD.bazel generated
View File

@ -4,7 +4,7 @@ load("@npm//:defs.bzl", "npm_link_all_packages")
load("//client/shared/dev:generate_graphql_operations.bzl", "generate_graphql_operations")
load("//client/shared/dev:tools.bzl", "module_style_typings")
load("//dev:defs.bzl", "jest_test", "npm_package", "sass", "ts_project")
load("//dev:webpack.bzl", "webpack_bundle", "webpack_devserver")
load("//dev:webpack.bzl", "webpack_bundle", "webpack_devserver", "webpack_web_app")
# TODO(bazel): storybook build
# gazelle:exclude **/*.story.{ts,tsx}
@ -1939,8 +1939,8 @@ ENTERPRISE_BUNDLE_DATA_DEPS = BUNDLE_DATA_DEPS + [
]
]
webpack_bundle(
name = "bundle-enterprise",
webpack_web_app(
name = "app-enterprise",
srcs = ENTERPRISE_BUNDLE_DATA_DEPS + [
"//:babel_config",
"//:browserslist",
@ -1951,8 +1951,11 @@ webpack_bundle(
},
env = {
"NODE_ENV": "production",
"ENTERPRISE": "true",
"INTEGRATION_TESTS": "true",
},
output_dir = True,
visibility = ["//client/web:__subpackages__"],
webpack_config = "webpack.bazel.config.js",
deps = WEBPACK_CONFIG_DEPS,
)
@ -1984,6 +1987,6 @@ build_test(
name = "webpack_test",
targets = [
":bundle",
":bundle-enterprise",
":app-enterprise",
],
)

View File

@ -66,7 +66,7 @@ describe('Repository component', () => {
await driver.page.waitForSelector(selector, { visible: true })
return driver.page.evaluate(() =>
// You can't reference hoverContentSelector in puppeteer's driver.page.evaluate
[...document.querySelectorAll('.test-tooltip-content')].map(content => content.textContent || '')
Array.from(document.querySelectorAll('.test-tooltip-content')).map(content => content.textContent || '')
)
}

View File

@ -1,5 +1,6 @@
load("@aspect_rules_ts//ts:defs.bzl", "ts_config")
load("//dev:defs.bzl", "ts_project")
load("//dev:mocha.bzl", "mocha_test")
# integration/ does not contain a src/
# gazelle:js_files **/*.{ts,tsx}
@ -99,3 +100,14 @@ ts_project(
"//client/web:node_modules/@sourcegraph/shared",
],
)
mocha_test(
name = "integration-tests",
data = ["//client/web:app-enterprise"],
env = {
"WEB_BUNDLE_PATH": "$(rootpath //client/web:app-enterprise)",
},
tags = ["manual"],
tests = [test.replace(".ts", ".js") for test in glob(["**/*.test.ts"])],
deps = [":integration_tests"],
)

View File

@ -13,14 +13,19 @@ NON_BUNDLED = [
"node-fetch",
"console",
# UMD modules
"jsonc-parser",
# Dependencies with bundling issues
"jsonc-parser"
"@sourcegraph/build-config",
]
# ... some of which are needed at runtime
NON_BUNDLED_DEPS = [
"//:node_modules/jsonc-parser",
"//:node_modules/puppeteer",
"//:node_modules/axe-core",
"//client/web:node_modules/@sourcegraph/build-config",
]
def mocha_test(name, tests, deps = [], args = [], data = [], env = {}, **kwargs):
@ -32,7 +37,7 @@ def mocha_test(name, tests, deps = [], args = [], data = [], env = {}, **kwargs)
testonly = True,
entry_points = tests,
platform = "node",
target = "node12",
target = "esnext",
output_dir = True,
external = NON_BUNDLED,
sourcemap = "linked",
@ -52,7 +57,7 @@ def mocha_test(name, tests, deps = [], args = [], data = [], env = {}, **kwargs)
"$(location //:mocha_config)",
"$(location :%s)/**/*.test.js" % bundle_name,
] + args,
data = data + deps + [
data = data + [
":%s" % bundle_name,
"//:mocha_config",
] + NON_BUNDLED_DEPS,
@ -67,11 +72,16 @@ def mocha_test(name, tests, deps = [], args = [], data = [], env = {}, **kwargs)
"SOURCEGRAPH_BASE_URL": "https://sourcegraph.test:3443",
"GH_TOKEN": "fake-gh-token",
"SOURCEGRAPH_SUDO_TOKEN": "fake-sg-token",
"NO_CLEANUP": "true",
"KEEP_BROWSER": "true",
"DEVTOOLS": "true",
"NO_CLEANUP": "false",
"KEEP_BROWSER": "false",
"DEVTOOLS": "false",
"BROWSER": "chrome",
"WINDOW_WIDTH": "1920",
"WINDOW_HEIGHT": "1080",
"LOG_BROWSER_CONSOLE": "false",
# Puppeteer config
"DISPLAY": ":1",
}),
tags = ["manual"],
**kwargs
)

View File

@ -1,4 +1,5 @@
load("@aspect_rules_webpack//webpack:defs.bzl", _webpack_bundle = "webpack_bundle", _webpack_devserver = "webpack_devserver")
load("@aspect_bazel_lib//lib:copy_to_directory.bzl", "copy_to_directory")
def webpack_bundle(name, **kwargs):
_webpack_bundle(
@ -7,6 +8,24 @@ def webpack_bundle(name, **kwargs):
**kwargs
)
def webpack_web_app(name, **kwargs):
bundle_name = "%s_bundle" % name
_webpack_bundle(
name = bundle_name,
webpack = "//dev:webpack",
**kwargs
)
copy_to_directory(
name = name,
# flatten static assets
# https://docs.aspect.build/rules/aspect_bazel_lib/docs/copy_to_directory/#root_paths
root_paths = ["ui/assets", "client/web/%s" % bundle_name],
srcs = ["//ui/assets/img:img", ":%s" % bundle_name],
visibility = ["//visibility:public"]
)
def webpack_devserver(name, **kwargs):
_webpack_devserver(
name = name,

View File

@ -38,6 +38,10 @@ Additional `BUILD.bazel` files may exist throughout subdirectories and is encour
All client tests (of all types such as jest and mocha) can be invoked by `bazel test //client/...` or individual tests can be specified such as `bazel test //client/common:test` or `bazel test //client/web/src/end-to-end:e2e`. Jest tests can be debugged using `bazel run --config=debug //client/common:test`.
### Notes
Currently, it's impossible to use features that Babel will transpile, creating helper methods inside Puppeteer `driver.page.evaluate` calls. E.g., the `for-of` syntax transpiled by Babel creates a helper in the module's top-level scope and uses it in the `evaluate` call. But since the contents of the `evaluate` call are passed to the `eval` function inside Puppeteer, it doesn't have the reference to the created helper and fails in the runtime. This is caused by the fact that we uniformly transform all TS files to JS using Babel in Bazel. We will develop an approach that would allow skipping the Babel transpilation step for files executed only in the node environment.
## Bundling
The primary `client/web` bundle targets are:

View File

@ -1,4 +1,5 @@
filegroup(
name = "img",
srcs = glob(["**/*.*"]),
visibility = ["//visibility:public"],
)