From e8d65f22d2aefcf576023ecb5130e1562799986e Mon Sep 17 00:00:00 2001 From: Bartlomiej Obecny Date: Mon, 8 Nov 2021 23:05:38 +0100 Subject: [PATCH] chore: adding selenium tests with browserstack (#2570) Co-authored-by: Valentin Marchaud --- .github/workflows/peer-api.yaml | 5 +- .github/workflows/unit-test.yml | 4 +- lerna.json | 3 +- selenium-tests/.gitignore | 2 + selenium-tests/README.md | 54 ++++ selenium-tests/babel.config.js | 33 +++ selenium-tests/example.env | 2 + selenium-tests/nightwatch.conf.js | 287 ++++++++++++++++++++++ selenium-tests/package.json | 70 ++++++ selenium-tests/pages/fetch/index.html | 20 ++ selenium-tests/pages/fetch/index.js | 51 ++++ selenium-tests/pages/helper.js | 46 ++++ selenium-tests/pages/tracing.js | 32 +++ selenium-tests/pages/xhr/index.html | 20 ++ selenium-tests/pages/xhr/index.js | 56 +++++ selenium-tests/scripts/local.runner.js | 38 +++ selenium-tests/tests-helpers/constants.js | 5 + selenium-tests/tests/fetch/fetch.js | 27 ++ selenium-tests/tests/xhr/xhr.js | 26 ++ selenium-tests/webpack.common.js | 38 +++ selenium-tests/webpack.config.js | 57 +++++ selenium-tests/webpack.dev.js | 11 + selenium-tests/webpack.production.js | 17 ++ 23 files changed, 899 insertions(+), 5 deletions(-) create mode 100644 selenium-tests/.gitignore create mode 100644 selenium-tests/README.md create mode 100644 selenium-tests/babel.config.js create mode 100644 selenium-tests/example.env create mode 100644 selenium-tests/nightwatch.conf.js create mode 100644 selenium-tests/package.json create mode 100644 selenium-tests/pages/fetch/index.html create mode 100644 selenium-tests/pages/fetch/index.js create mode 100644 selenium-tests/pages/helper.js create mode 100644 selenium-tests/pages/tracing.js create mode 100644 selenium-tests/pages/xhr/index.html create mode 100644 selenium-tests/pages/xhr/index.js create mode 100755 selenium-tests/scripts/local.runner.js create mode 100644 selenium-tests/tests-helpers/constants.js create mode 100644 selenium-tests/tests/fetch/fetch.js create mode 100644 selenium-tests/tests/xhr/xhr.js create mode 100644 selenium-tests/webpack.common.js create mode 100644 selenium-tests/webpack.config.js create mode 100644 selenium-tests/webpack.dev.js create mode 100644 selenium-tests/webpack.production.js diff --git a/.github/workflows/peer-api.yaml b/.github/workflows/peer-api.yaml index ce642134d0..f4f1731ea9 100644 --- a/.github/workflows/peer-api.yaml +++ b/.github/workflows/peer-api.yaml @@ -22,8 +22,9 @@ jobs: run: npm install semver - name: Check API dependency semantics (stable) - run: lerna exec --ignore propagation-validation-server "node ../../scripts/peer-api-check.js" + working-directory: packages + run: lerna exec --ignore propagation-validation-server --ignore propagation-validation-server --ignore @opentelemetry/selenium-tests "node ../../scripts/peer-api-check.js" - name: Check API dependency semantics (experimental) working-directory: experimental - run: lerna exec --ignore propagation-validation-server "node ../../../scripts/peer-api-check.js" + run: lerna exec --ignore propagation-validation-server --ignore @opentelemetry/selenium-tests "node ../../../scripts/peer-api-check.js" diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml index 6352235f8b..4cc9daca63 100644 --- a/.github/workflows/unit-test.yml +++ b/.github/workflows/unit-test.yml @@ -35,7 +35,7 @@ jobs: if: steps.cache.outputs.cache-hit != 'true' run: | npm install --ignore-scripts - npx lerna bootstrap --no-ci --hoist --nohoist='zone.js' + npx lerna bootstrap --no-ci --hoist --nohoist='zone.js' --ignore @opentelemetry/selenium-tests - name: Build 🔧 run: | @@ -111,7 +111,7 @@ jobs: working-directory: experimental run: | npm install --ignore-scripts - npx lerna bootstrap --no-ci --hoist --nohoist='zone.js' + npx lerna bootstrap --no-ci --hoist --nohoist='zone.js' --ignore @opentelemetry/selenium-tests - name: Build 🔧 working-directory: experimental diff --git a/lerna.json b/lerna.json index b530144ffc..2c0b8bd664 100644 --- a/lerna.json +++ b/lerna.json @@ -5,6 +5,7 @@ "packages": [ "benchmark/*", "packages/*", - "integration-tests/*" + "integration-tests/*", + "selenium-tests" ] } diff --git a/selenium-tests/.gitignore b/selenium-tests/.gitignore new file mode 100644 index 0000000000..53b77116ea --- /dev/null +++ b/selenium-tests/.gitignore @@ -0,0 +1,2 @@ +tests_output +tmp diff --git a/selenium-tests/README.md b/selenium-tests/README.md new file mode 100644 index 0000000000..eda21150fc --- /dev/null +++ b/selenium-tests/README.md @@ -0,0 +1,54 @@ +## Selenium Tests + +Selenium tests are to help verify the working of opentelemetry in different browsers. For that the nightwatch is used. +This can be run either locally or in github actions with usage of browserstack. +Browser stack also gives possibility of running tests in browserstack on different browsers using our local environment. +This helps to test and debug things locally using any browser we want. + +## Running tests locally using local browser - this is also useful when adding new test + +1. run server + +```shell +npm run server +``` + +1. run local test for example for xhr + +```shell +npm run local:xhr +``` + +Please wait a bit it should run selenium tests using our local version of chrome + +## Running tests locally using browser stack account - for that you need to have a browser stack account + +If you have it please create a file ".env" based on "example.env" + +1. run server + +```shell +npm run server +``` + +1. Run local test for example for xhr + +```shell +npm run local:bs:xhr +``` + +## Architecture + +1. Folder pages contains all the pages that can be entered after running `npn run server` at . +These are fully functioning pages and can be run without running tests. + +2. To be able to test it automatically instead of manually we have to create a test. Tests are kept in folder "tests". +For each page there is a corresponding folder with exactly the same name. There are additional 2 files + +- helper.js - this file keeps some helpers functions that are included in page automatically and they are available in tests only in section "execute". +This is because this section is being sent to the browser and executed automatically. This is the only way of "sending" and "reading" data back to test +When data is being sent between browser and selenium it needs to be stringified that's why we have one more helper for that called "JSONSafeStringify" +The last helper is the one that helps to verify test has finished successfully "OTELSeleniumDone". + +- tracing.js - this file contains the skeleton for tracing and export function "loadOtel" this is a helper function that accepts instrumentations as param. +This way it is easy to add different tests that has different lists of instrumentations. diff --git a/selenium-tests/babel.config.js b/selenium-tests/babel.config.js new file mode 100644 index 0000000000..26b15edca0 --- /dev/null +++ b/selenium-tests/babel.config.js @@ -0,0 +1,33 @@ +module.exports = function (api) { + api.cache(true); + const presets = [ + [ + '@babel/preset-env', + { + corejs: { + version: '3', + proposals: true, + }, + useBuiltIns: 'entry', + targets: { + // 'edge': 16, + // 'safari': 9, + // 'firefox': 57, + 'ie': 11, + // 'ios': 9, + // 'chrome': 49, + }, + }, + ], + ]; + const plugins = [ + ['@babel/plugin-proposal-decorators', { decoratorsBeforeExport: true }], + ['@babel/plugin-proposal-class-properties', { 'loose': true }], + ["@babel/plugin-proposal-private-methods", { "loose": true }], + ["@babel/plugin-proposal-private-property-in-object", { "loose": true }], + ]; + return { + presets, + plugins, + }; +}; \ No newline at end of file diff --git a/selenium-tests/example.env b/selenium-tests/example.env new file mode 100644 index 0000000000..28fc9739b0 --- /dev/null +++ b/selenium-tests/example.env @@ -0,0 +1,2 @@ +BROWSERSTACK_USER="YOUR USER" +BROWSERSTACK_KEY="YOUR BROWSER STACK KEY" \ No newline at end of file diff --git a/selenium-tests/nightwatch.conf.js b/selenium-tests/nightwatch.conf.js new file mode 100644 index 0000000000..8547bc2422 --- /dev/null +++ b/selenium-tests/nightwatch.conf.js @@ -0,0 +1,287 @@ +// Autogenerated by Nightwatch +// Refer to the online docs for more details: https://nightwatchjs.org/gettingstarted/configuration/ +const Services = {}; loadServices(); + +module.exports = { + // An array of folders (excluding subfolders) where your tests are located; + // if this is not specified, the test source must be passed as the second argument to the test runner. + src_folders: [], + + // See https://nightwatchjs.org/guide/working-with-page-objects/ + page_objects_path: '', + + // See https://nightwatchjs.org/guide/extending-nightwatch/#writing-custom-commands + custom_commands_path: '', + + // See https://nightwatchjs.org/guide/extending-nightwatch/#writing-custom-assertions + custom_assertions_path: '', + + // See https://nightwatchjs.org/guide/#external-globals + globals_path : '', + + webdriver: {}, + + test_settings: { + default: { + disable_error_log: false, + launch_url: 'https://nightwatchjs.org', + + screenshots: { + enabled: false, + path: 'screens', + on_failure: true + }, + + desiredCapabilities: { + browserName : 'firefox' + }, + + webdriver: { + start_process: true, + server_path: (Services.geckodriver ? Services.geckodriver.path : '') + } + }, + + safari: { + desiredCapabilities : { + browserName : 'safari', + alwaysMatch: { + acceptInsecureCerts: false + } + }, + webdriver: { + port: 4445, + start_process: true, + server_path: '/usr/bin/safaridriver' + } + }, + + firefox: { + desiredCapabilities : { + browserName : 'firefox', + alwaysMatch: { + acceptInsecureCerts: true, + 'moz:firefoxOptions': { + args: [ + // '-headless', + // '-verbose' + ] + } + } + + }, + webdriver: { + start_process: true, + port: 4444, + server_path: (Services.geckodriver ? Services.geckodriver.path : ''), + cli_args: [ + // very verbose geckodriver logs + // '-vv' + ] + } + }, + + chrome: { + desiredCapabilities : { + browserName : 'chrome', + 'goog:chromeOptions' : { + // More info on Chromedriver: https://sites.google.com/a/chromium.org/chromedriver/ + // + // This tells Chromedriver to run using the legacy JSONWire protocol (not required in Chrome 78) + w3c: false, + args: [ + //'--no-sandbox', + //'--ignore-certificate-errors', + //'--allow-insecure-localhost', + //'--headless' + ] + } + }, + + webdriver: { + start_process: true, + port: 9515, + server_path: (Services.chromedriver ? Services.chromedriver.path : ''), + cli_args: [ + // --verbose + ] + } + }, + + edge: { + desiredCapabilities : { + browserName : 'MicrosoftEdge', + 'ms:edgeOptions' : { + w3c: false, + // More info on EdgeDriver: https://docs.microsoft.com/en-us/microsoft-edge/webdriver-chromium/capabilities-edge-options + args: [ + //'--headless' + ] + } + }, + + webdriver: { + start_process: true, + // Download msedgedriver from https://docs.microsoft.com/en-us/microsoft-edge/webdriver-chromium/ + // and set the location below: + server_path: '', + cli_args: [ + // --verbose + ] + } + }, + + ////////////////////////////////////////////////////////////////////////////////// + // Configuration for when using the browserstack.com cloud service | + // | + // Please set the username and access key by setting the environment variables: | + // - BROWSERSTACK_USER | + // - BROWSERSTACK_KEY | + // .env files are supported | + ////////////////////////////////////////////////////////////////////////////////// + browserstack: { + selenium: { + host: 'hub-cloud.browserstack.com', + port: 443 + }, + // More info on configuring capabilities can be found on: + // https://www.browserstack.com/automate/capabilities?tag=selenium-4 + desiredCapabilities: { + 'bstack:options' : { + userName: '${BROWSERSTACK_USER}', + accessKey: '${BROWSERSTACK_KEY}', + } + }, + + disable_error_log: true, + webdriver: { + timeout_options: { + timeout: 15000, + retry_attempts: 3 + }, + keep_alive: true, + start_process: false + } + }, + + 'browserstack.local': { + extends: 'browserstack', + desiredCapabilities: { + 'browserstack.local': true, + 'browserstack.console': 'errors' + } + }, + + 'browserstack.chrome': { + extends: 'browserstack', + desiredCapabilities: { + browserName: 'chrome', + chromeOptions : { + w3c: false + } + } + }, + + 'browserstack.firefox': { + extends: 'browserstack', + desiredCapabilities: { + browserName: 'firefox' + } + }, + + 'browserstack.ie': { + extends: 'browserstack', + desiredCapabilities: { + browserName: 'internet explorer', + browserVersion: '11.0' + } + }, + + 'browserstack.safari': { + extends: 'browserstack', + desiredCapabilities: { + browserName: 'safari' + } + }, + + 'browserstack.local_chrome': { + extends: 'browserstack.local', + desiredCapabilities: { + browserName: 'chrome' + } + }, + + 'browserstack.local_firefox': { + extends: 'browserstack.local', + desiredCapabilities: { + browserName: 'firefox' + } + }, + 'browserstack.local_ie': { + extends: 'browserstack.local', + desiredCapabilities: { + browserName: 'internet explorer', + browserVersion: '11.0' + } + }, + 'browserstack.local_safari': { + extends: 'browserstack.local', + desiredCapabilities: { + browserName: 'safari', + } + }, + ////////////////////////////////////////////////////////////////////////////////// + // Configuration for when using the Selenium service, either locally or remote, | + // like Selenium Grid | + ////////////////////////////////////////////////////////////////////////////////// + selenium_server: { + // Selenium Server is running locally and is managed by Nightwatch + selenium: { + start_process: true, + port: 4444, + server_path: (Services.seleniumServer ? Services.seleniumServer.path : ''), + cli_args: { + 'webdriver.gecko.driver': (Services.geckodriver ? Services.geckodriver.path : ''), + 'webdriver.chrome.driver': (Services.chromedriver ? Services.chromedriver.path : '') + } + } + }, + + 'selenium.chrome': { + extends: 'selenium_server', + desiredCapabilities: { + browserName: 'chrome', + chromeOptions : { + w3c: false, + } + } + }, + + 'selenium.firefox': { + extends: 'selenium_server', + desiredCapabilities: { + browserName: 'firefox', + 'moz:firefoxOptions': { + args: [ + // '-headless', + // '-verbose' + ] + } + } + } + } +}; + +function loadServices() { + try { + Services.seleniumServer = require('selenium-server'); + } catch (err) {} + + try { + Services.chromedriver = require('chromedriver'); + } catch (err) {} + + try { + Services.geckodriver = require('geckodriver'); + } catch (err) {} +} diff --git a/selenium-tests/package.json b/selenium-tests/package.json new file mode 100644 index 0000000000..fd78e47105 --- /dev/null +++ b/selenium-tests/package.json @@ -0,0 +1,70 @@ +{ + "name": "@opentelemetry/selenium-tests", + "version": "0.0.1", + "description": "OpenTelemetry Selenium Tests", + "main": "index.js", + "repository": "open-telemetry/opentelemetry-js", + "scripts": { + "all:bs": "nightwatch ./tests --parallel --env browserstack.chrome,browserstack.safari,browserstack.firefox", + "all:local": "nightwatch ./tests --parallel --env selenium.chrome", + "local:bs:fetch": "node scripts/local.runner.js --test ./tests/fetch/fetch.js --parallel --env browserstack.local_chrome,browserstack.local_firefox,browserstack.local_safari", + "local:bs:xhr": "node scripts/local.runner.js --test ./tests/xhr/xhr.js --parallel --env browserstack.local_chrome,browserstack.local_firefox,browserstack.local_ie,browserstack.local_safari", + "local:fetch": "nightwatch ./tests/fetch/fetch.js --env selenium.chrome", + "local:xhr": "nightwatch ./tests/xhr/xhr.js --env selenium.chrome", + "server": "webpack serve --progress --port 8090 --config webpack.dev.js --hot --host 0.0.0.0" + }, + "keywords": [ + "opentelemetry", + "web", + "tracing", + "profiling", + "metrics", + "stats" + ], + "author": "OpenTelemetry Authors", + "license": "Apache-2.0", + "engines": { + "node": ">=10.0.0" + }, + "publishConfig": { + "access": "private" + }, + "devDependencies": { + "@babel/core": "^7.15.8", + "@babel/plugin-proposal-class-properties": "^7.14.5", + "@babel/plugin-proposal-decorators": "^7.15.8", + "@babel/plugin-transform-runtime": "^7.15.8", + "@babel/preset-env": "^7.15.0", + "@opentelemetry/api": "^1.0.3", + "babel-loader": "^8.2.3", + "babel-polyfill": "^6.26.0", + "browserstack-local": "^1.4.8", + "chromedriver": "^95.0.0", + "dotenv": "^10.0.0", + "fast-safe-stringify": "^2.1.1", + "geckodriver": "^2.0.4", + "nightwatch": "^1.7.11", + "selenium-server": "^3.141.59", + "terser-webpack-plugin": "^5.2.4", + "webpack": "^5.60.0", + "webpack-cli": "^4.9.1", + "webpack-dev-server": "^4.3.1", + "webpack-merge": "^5.8.0" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.0.3" + }, + "dependencies": { + "@opentelemetry/context-zone-peer-dep": "^1.0.0", + "@opentelemetry/core": "^1.0.0", + "@opentelemetry/exporter-otlp-http": "^0.26.0", + "@opentelemetry/exporter-zipkin": "^1.0.0", + "@opentelemetry/instrumentation": "^0.26.0", + "@opentelemetry/instrumentation-fetch": "^0.26.0", + "@opentelemetry/instrumentation-xml-http-request": "^0.26.0", + "@opentelemetry/sdk-metrics-base": "^0.26.0", + "@opentelemetry/sdk-trace-base": "^1.0.0", + "@opentelemetry/sdk-trace-web": "^1.0.0", + "zone.js": "^0.10.3" + } +} diff --git a/selenium-tests/pages/fetch/index.html b/selenium-tests/pages/fetch/index.html new file mode 100644 index 0000000000..a43e52d2ef --- /dev/null +++ b/selenium-tests/pages/fetch/index.html @@ -0,0 +1,20 @@ + + + + + + Fetch Plugin + + + + + + + Example of using Web Tracer with Fetch plugin and console exporter + +
+ + + + + diff --git a/selenium-tests/pages/fetch/index.js b/selenium-tests/pages/fetch/index.js new file mode 100644 index 0000000000..dbd2fe1166 --- /dev/null +++ b/selenium-tests/pages/fetch/index.js @@ -0,0 +1,51 @@ +'use strict'; +import '../helper'; + +import { context, trace } from '@opentelemetry/api'; + +import { FetchInstrumentation } from '@opentelemetry/instrumentation-fetch'; +import { loadOtel } from '../tracing'; + +const provider = loadOtel([ + new FetchInstrumentation({ + ignoreUrls: [/localhost:8090\/sockjs-node/], + propagateTraceHeaderCorsUrls: [ + 'https://cors-test.appspot.com/test', + 'https://httpbin.org/get', + ], + clearTimingResources: true, + }), +]); + +const webTracerWithZone = provider.getTracer('example-tracer-web'); + +const getData = (url) => fetch(url, { + method: 'GET', + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + }, +}); + +// example of keeping track of context between async operations +const prepareClickEvent = () => { + const url = 'https://httpbin.org/get'; + + const element = document.getElementById('button1'); + + const onClick = () => { + const singleSpan = webTracerWithZone.startSpan(`files-series-info`); + context.with(trace.setSpan(context.active(), singleSpan), () => { + getData(url).then((_data) => { + trace.getSpan(context.active()).addEvent('fetching-single-span-completed'); + singleSpan.end(); + }).finally(()=> { + otel.OTELSeleniumDone(); + console.log(otel.memoryExporter.getFinishedSpans()); + }); + }); + }; + element.addEventListener('click', onClick); +}; + +window.addEventListener('load', prepareClickEvent); diff --git a/selenium-tests/pages/helper.js b/selenium-tests/pages/helper.js new file mode 100644 index 0000000000..8f7c23bfd8 --- /dev/null +++ b/selenium-tests/pages/helper.js @@ -0,0 +1,46 @@ +/** + * These are all needed to make otel to work correctly for IE + */ +window.__Zone_enable_cross_context_check = true; +import 'babel-polyfill'; +import 'zone.js'; + +const safeStringify = require('fast-safe-stringify'); + +/** + * Function to stringify data between browser and selenium + * It works fine for circular dependency + * @param obj + * @return {*|string} + * @constructor + */ +function JSONSafeStringify(obj) { + return safeStringify( + obj, + function replacer(key, value) { + // Remove the circular structure + if (value === '[Circular]') { + return; + } + return value; + }, + 2, + ); +} + +/** + * Function helper to mark action on page as finished. This way selenium understands that test is finished + */ +function OTELSeleniumDone() { + const element = document.createElement('div'); + element.setAttribute('id', 'otelSeleniumDone'); + element.innerHTML = 'OTELSeleniumDone'; + window.setTimeout(() => { + document.body.appendChild(element); + }, 1000); +} + +window.otel = Object.assign({}, window.otel, { + JSONSafeStringify: JSONSafeStringify, + OTELSeleniumDone: OTELSeleniumDone, +}); diff --git a/selenium-tests/pages/tracing.js b/selenium-tests/pages/tracing.js new file mode 100644 index 0000000000..6dd6e5f26f --- /dev/null +++ b/selenium-tests/pages/tracing.js @@ -0,0 +1,32 @@ +'use strict'; + +import { ConsoleSpanExporter, InMemorySpanExporter, SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base'; +import { WebTracerProvider } from '@opentelemetry/sdk-trace-web'; +import { ZoneContextManager } from '@opentelemetry/context-zone-peer-dep'; +import { registerInstrumentations } from '@opentelemetry/instrumentation'; + +/** + * Function helper to load the tracing with predefined instrumentations + * @param instrumentations + * @return {WebTracerProvider} + */ +export function loadOtel(instrumentations) { + const provider = new WebTracerProvider(); + const memoryExporter = new InMemorySpanExporter(); + provider.addSpanProcessor(new SimpleSpanProcessor(memoryExporter)); + provider.addSpanProcessor(new SimpleSpanProcessor(new ConsoleSpanExporter())); + provider.register({ + contextManager: new ZoneContextManager(), + }); + + registerInstrumentations({ + instrumentations: [ + instrumentations, + ], + }); + window.otel = Object.assign({}, window.otel, { + provider, + memoryExporter, + }); + return provider; +} \ No newline at end of file diff --git a/selenium-tests/pages/xhr/index.html b/selenium-tests/pages/xhr/index.html new file mode 100644 index 0000000000..0914215d3c --- /dev/null +++ b/selenium-tests/pages/xhr/index.html @@ -0,0 +1,20 @@ + + + + + + XHR Plugin + + + + + + + Example of using Web Tracer with XHR plugin and console exporter + +
+ + + + + diff --git a/selenium-tests/pages/xhr/index.js b/selenium-tests/pages/xhr/index.js new file mode 100644 index 0000000000..e998c58962 --- /dev/null +++ b/selenium-tests/pages/xhr/index.js @@ -0,0 +1,56 @@ +'use strict'; +import '../helper'; + +import { context, trace } from '@opentelemetry/api'; + +import { XMLHttpRequestInstrumentation } from '@opentelemetry/instrumentation-xml-http-request'; +import { loadOtel } from '../tracing'; + +const provider = loadOtel([ + new XMLHttpRequestInstrumentation({ + ignoreUrls: [/localhost:8090\/sockjs-node/], + propagateTraceHeaderCorsUrls: [ + 'https://httpbin.org/get', + ], + clearTimingResources: true, + }), +]); + +const webTracerWithZone = provider.getTracer('example-tracer-web'); + +const getData = (url) => new Promise((resolve, reject) => { + // eslint-disable-next-line no-undef + const req = new XMLHttpRequest(); + req.open('GET', url, true); + req.setRequestHeader('Content-Type', 'application/json'); + req.setRequestHeader('Accept', 'application/json'); + req.onload = () => { + resolve(); + }; + req.onerror = () => { + reject(); + }; + req.send(); +}); + +// example of keeping track of context between async operations +const prepareClickEvent = () => { + const url = 'https://httpbin.org/get'; + + const element = document.getElementById('button1'); + + const onClick = () => { + const singleSpan = webTracerWithZone.startSpan(`files-series-info`); + context.with(trace.setSpan(context.active(), singleSpan), () => { + getData(url).then((_data) => { + trace.getSpan(context.active()).addEvent('fetching-single-span-completed'); + singleSpan.end(); + }).finally(() => { + otel.OTELSeleniumDone(); + }); + }); + }; + element.addEventListener('click', onClick); +}; + +window.addEventListener('load', prepareClickEvent); diff --git a/selenium-tests/scripts/local.runner.js b/selenium-tests/scripts/local.runner.js new file mode 100755 index 0000000000..295b4b2c61 --- /dev/null +++ b/selenium-tests/scripts/local.runner.js @@ -0,0 +1,38 @@ +#!/usr/bin/env node + +const Nightwatch = require('nightwatch'); +const browserstack = require('browserstack-local'); +require('dotenv').config(); + +let bs_local; + +try { + require.main.filename = './node_modules/.bin/nightwatch'; + // Code to start browserstack local before start of test + console.log('Connecting local'); + Nightwatch.bs_local = bs_local = new browserstack.Local(); + bs_local.start({ key: process.env.BROWSERSTACK_KEY }, function (error) { + if (error) { + bs_local.stop(function () {}); + throw error; + } + + console.log('Connected. Now testing...'); + Nightwatch.cli(function (argv) { + Nightwatch.CliRunner(argv) + .setup() + .runTests() + .catch((err) => { + throw err; + }) + .finally(() => { + // Code to stop browserstack local after end of single test + bs_local.stop(function () {}); + }); + }); + }); +} catch (ex) { + console.log('There was an error while starting the test runner:\n\n'); + process.stderr.write(ex.stack + '\n'); + process.exit(2); +} diff --git a/selenium-tests/tests-helpers/constants.js b/selenium-tests/tests-helpers/constants.js new file mode 100644 index 0000000000..3362a3585c --- /dev/null +++ b/selenium-tests/tests-helpers/constants.js @@ -0,0 +1,5 @@ +const TIMEOUT_ELEMENT_MS = 5000; + +module.exports = { + TIMEOUT_ELEMENT_MS, +}; \ No newline at end of file diff --git a/selenium-tests/tests/fetch/fetch.js b/selenium-tests/tests/fetch/fetch.js new file mode 100644 index 0000000000..c60a5e1b87 --- /dev/null +++ b/selenium-tests/tests/fetch/fetch.js @@ -0,0 +1,27 @@ +const { TIMEOUT_ELEMENT_MS } = require('../../tests-helpers/constants'); + +module.exports = { + 'Fetch instrumentation': function (browser) { + browser + .url('http://localhost:8090/fetch') + .waitForElementVisible('body', TIMEOUT_ELEMENT_MS) + .waitForElementVisible('#button1', TIMEOUT_ELEMENT_MS) + .click('#button1') + .waitForElementVisible('#otelSeleniumDone', TIMEOUT_ELEMENT_MS) + .execute( + () => { + const spans = otel.memoryExporter.getFinishedSpans(); + return otel.JSONSafeStringify(spans); + }, + [], + (result) => { + const spans = JSON.parse(result.value); + browser.assert.equal(spans.length, 2, 'Should export 2 spans'); + }, + ) + + browser.end(() => { + console.log('end'); + }); + }, +}; diff --git a/selenium-tests/tests/xhr/xhr.js b/selenium-tests/tests/xhr/xhr.js new file mode 100644 index 0000000000..5204f527ff --- /dev/null +++ b/selenium-tests/tests/xhr/xhr.js @@ -0,0 +1,26 @@ +const { TIMEOUT_ELEMENT_MS } = require('../../tests-helpers/constants'); +module.exports = { + 'XHR instrumentation': function (browser) { + browser + .url('http://localhost:8090/xhr') + .waitForElementVisible('body', TIMEOUT_ELEMENT_MS) + .waitForElementVisible('#button1', TIMEOUT_ELEMENT_MS) + .click('#button1') + .waitForElementVisible('#otelSeleniumDone', TIMEOUT_ELEMENT_MS) + .execute( + function() { + const spans = otel.memoryExporter.getFinishedSpans(); + return otel.JSONSafeStringify(spans); + }, + [], + function(result) { + const spans = JSON.parse(result.value); + browser.assert.equal(spans.length, 2, 'Should export 2 spans'); + }, + ) + + browser.end(() => { + console.log('end'); + }); + }, +}; diff --git a/selenium-tests/webpack.common.js b/selenium-tests/webpack.common.js new file mode 100644 index 0000000000..0d00b07c60 --- /dev/null +++ b/selenium-tests/webpack.common.js @@ -0,0 +1,38 @@ +const path = require('path'); +const directory = path.resolve(__dirname); + +module.exports = { + entry: { + fetch: 'pages/fetch/index.js', + xhr: 'pages/xhr/index.js', + }, + output: { + path: path.resolve(__dirname, 'dist'), + filename: '[name].js', + sourceMapFilename: '[file].map', + }, + target: ['web', 'es5'], + module: { + rules: [ + { + test: /\.js[x]?$/, + exclude: /(node_modules)/, + use: { + loader: 'babel-loader', + options: { + babelrc: false, + configFile: path.resolve(__dirname, 'babel.config.js'), + presets: ['@babel/preset-env'], + }, + }, + }, + ], + }, + resolve: { + modules: [ + path.resolve(directory), + 'node_modules', + ], + extensions: ['.ts', '.js', '.jsx', '.json'], + }, +}; \ No newline at end of file diff --git a/selenium-tests/webpack.config.js b/selenium-tests/webpack.config.js new file mode 100644 index 0000000000..061ab2a770 --- /dev/null +++ b/selenium-tests/webpack.config.js @@ -0,0 +1,57 @@ +const webpack = require('webpack'); +const webpackMerge = require('webpack-merge'); +const path = require('path'); + +const directory = path.resolve(__dirname); + +const common = { + mode: 'development', + entry: { + fetch: 'pages/fetch/index.js', + // xhr: 'pages/xhr/index.js', + }, + output: { + path: path.resolve(__dirname, 'dist'), + filename: '[name].js', + sourceMapFilename: '[file].map', + }, + target: 'web', + module: { + rules: [ + { + test: /\.js[x]?$/, + exclude: /(node_modules)/, + use: { + loader: 'babel-loader', + options: { + babelrc: false, + configFile: path.resolve(__dirname, 'babel.config.js'), + presets: ['@babel/preset-env'], + }, + }, + include: [ + path.resolve(__dirname, 'pages/helper.js'), + ], + }, + ], + }, + resolve: { + modules: [ + path.resolve(directory), + 'node_modules', + ], + extensions: ['.js', '.jsx', '.json'], + }, +}; + +module.exports = webpackMerge(common, { + devtool: 'eval-source-map', + devServer: { + static: path.resolve(__dirname), + }, + plugins: [ + new webpack.DefinePlugin({ + 'process.env.NODE_ENV': JSON.stringify('development'), + }), + ], +}); diff --git a/selenium-tests/webpack.dev.js b/selenium-tests/webpack.dev.js new file mode 100644 index 0000000000..abdc9f3d2b --- /dev/null +++ b/selenium-tests/webpack.dev.js @@ -0,0 +1,11 @@ +const { merge } = require('webpack-merge'); +const common = require('./webpack.common.js'); +const path = require('path'); + +module.exports = merge(common, { + mode: 'development', + // devtool: 'inline-source-map', + devServer: { + static: path.resolve(__dirname, 'pages'), + }, +}); \ No newline at end of file diff --git a/selenium-tests/webpack.production.js b/selenium-tests/webpack.production.js new file mode 100644 index 0000000000..d5fd171c2d --- /dev/null +++ b/selenium-tests/webpack.production.js @@ -0,0 +1,17 @@ +const { merge } = require('webpack-merge'); +const common = require('./webpack.common.js'); +const TerserPlugin = require('terser-webpack-plugin'); + +module.exports = merge(common, { + mode: 'production', + optimization: { + minimize: true, + minimizer: [new TerserPlugin({ + extractComments: false, + terserOptions: { + ie8: true, + safari10: true, + }, + })], + }, +}); \ No newline at end of file