diff --git a/client/build-config/src/paths.ts b/client/build-config/src/paths.ts index 846a0bb4f67..5e5c90ed564 100644 --- a/client/build-config/src/paths.ts +++ b/client/build-config/src/paths.ts @@ -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) diff --git a/client/shared/BUILD.bazel b/client/shared/BUILD.bazel index 99862d81f4a..87d1661055c 100644 --- a/client/shared/BUILD.bazel +++ b/client/shared/BUILD.bazel @@ -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", diff --git a/client/shared/dev/suppressPollyErrors.js b/client/shared/dev/suppressPollyErrors.js index e41fe3db627..1f0c0b6c0a6 100644 --- a/client/shared/dev/suppressPollyErrors.js +++ b/client/shared/dev/suppressPollyErrors.js @@ -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 diff --git a/client/shared/src/testing/BUILD.bazel b/client/shared/src/testing/BUILD.bazel index 344cbc31426..bf4ac4d21b2 100644 --- a/client/shared/src/testing/BUILD.bazel +++ b/client/shared/src/testing/BUILD.bazel @@ -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", diff --git a/client/shared/src/testing/driver.ts b/client/shared/src/testing/driver.ts index 81e46c4154e..8425fead153 100644 --- a/client/shared/src/testing/driver.ts +++ b/client/shared/src/testing/driver.ts @@ -47,10 +47,10 @@ export const oncePageEvent = (page: Page, event export const extractStyles = (page: puppeteer.Page): Promise => 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(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(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 { - 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') diff --git a/client/web/BUILD.bazel b/client/web/BUILD.bazel index abccd7bb46c..49d60db28f7 100644 --- a/client/web/BUILD.bazel +++ b/client/web/BUILD.bazel @@ -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", ], ) diff --git a/client/web/src/end-to-end/code-intel/repository-component.test.ts b/client/web/src/end-to-end/code-intel/repository-component.test.ts index 7afb97efedf..72157630490 100644 --- a/client/web/src/end-to-end/code-intel/repository-component.test.ts +++ b/client/web/src/end-to-end/code-intel/repository-component.test.ts @@ -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 || '') ) } diff --git a/client/web/src/integration/BUILD.bazel b/client/web/src/integration/BUILD.bazel index d52c95f7aac..7250448118f 100644 --- a/client/web/src/integration/BUILD.bazel +++ b/client/web/src/integration/BUILD.bazel @@ -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"], +) diff --git a/dev/mocha.bzl b/dev/mocha.bzl index 08246913b3f..d1d324a0c17 100644 --- a/dev/mocha.bzl +++ b/dev/mocha.bzl @@ -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 ) diff --git a/dev/webpack.bzl b/dev/webpack.bzl index 28c359e044c..4dd20f38ca3 100644 --- a/dev/webpack.bzl +++ b/dev/webpack.bzl @@ -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, diff --git a/doc/dev/background-information/bazel_web.md b/doc/dev/background-information/bazel_web.md index c07712f3d0e..a5df381d25b 100644 --- a/doc/dev/background-information/bazel_web.md +++ b/doc/dev/background-information/bazel_web.md @@ -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: diff --git a/ui/assets/img/BUILD.bazel b/ui/assets/img/BUILD.bazel index 6f9ef8b9701..c9f26b445e1 100644 --- a/ui/assets/img/BUILD.bazel +++ b/ui/assets/img/BUILD.bazel @@ -1,4 +1,5 @@ filegroup( name = "img", srcs = glob(["**/*.*"]), + visibility = ["//visibility:public"], )