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