From b67809e9b387c7736aa3a8fbabd0e83816fa240f Mon Sep 17 00:00:00 2001 From: Raju Ahmed Date: Tue, 23 Dec 2025 13:12:24 +0600 Subject: [PATCH 1/3] [FSSDK-12089] move /public directory to /vitest/ --- vitest.browser.config.mts | 2 ++ {public => vitest/public}/console-capture.js | 0 2 files changed, 2 insertions(+) rename {public => vitest/public}/console-capture.js (100%) diff --git a/vitest.browser.config.mts b/vitest.browser.config.mts index 8588b522b..f5c20e13c 100644 --- a/vitest.browser.config.mts +++ b/vitest.browser.config.mts @@ -139,6 +139,8 @@ export default defineConfig({ target: 'es2015', }, }, + // Serve public files from vitest/public directory + publicDir: 'vitest/public', server: { host: '0.0.0.0', // for safari, browserstack redirects localhost to bs-local.com diff --git a/public/console-capture.js b/vitest/public/console-capture.js similarity index 100% rename from public/console-capture.js rename to vitest/public/console-capture.js From 21fd8e1bf29c28bee7d8407495ac579a049dcd19 Mon Sep 17 00:00:00 2001 From: esrakartalOpt <102107327+esrakartalOpt@users.noreply.github.com> Date: Mon, 22 Dec 2025 14:29:22 -0600 Subject: [PATCH 2/3] [FSSDK-12168] Fix Dependabot alerts (#1126) * [FSSDK-12168] Fix Dependabot alerts * Correct the packages * Fixed remaining vulnerability issues --- package-lock.json | 190 +++++++++++++--------------------------------- package.json | 8 +- 2 files changed, 58 insertions(+), 140 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7db4331a8..a55aef60f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,12 +29,12 @@ "@typescript-eslint/parser": "^5.33.0", "@vitest/browser": "3.2.4", "chai": "^4.2.0", - "coveralls-next": "^4.2.0", + "coveralls-next": "^5.0.0", "eslint": "^8.21.0", "eslint-config-prettier": "^6.10.0", "eslint-plugin-local-rules": "^3.0.2", "eslint-plugin-prettier": "^3.1.2", - "happy-dom": "^16.6.0", + "happy-dom": "^20.0.11", "jiti": "^2.4.1", "karma": "^6.4.0", "karma-browserstack-launcher": "^1.5.1", @@ -62,7 +62,7 @@ "vite": "^6.4.1", "vitest": "^3.2.4", "webdriverio": "^9.21.0", - "webpack": "^5.74.0" + "webpack": "^5.94.0" }, "engines": { "node": ">=18.0.0" @@ -267,7 +267,6 @@ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "dev": true, - "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -277,7 +276,6 @@ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "dev": true, - "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -297,7 +295,6 @@ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", "dev": true, - "license": "MIT", "dependencies": { "@babel/template": "^7.27.2", "@babel/types": "^7.28.4" @@ -614,7 +611,6 @@ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", "dev": true, - "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" @@ -1348,23 +1344,13 @@ "camelcase": "^5.3.1", "find-up": "^4.1.0", "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", + "js-yaml": "^4.1.1", "resolve-from": "^5.0.0" }, "engines": { "node": ">=8" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", @@ -1379,20 +1365,6 @@ "node": ">=8" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", - "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -1808,7 +1780,6 @@ "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-2.2.0.tgz", "integrity": "sha512-gvRvjR5JAaUZF8tv2Kcq/Gbt3JHwbKFYfmb445rhOj6NUMx3qPLixmDx5pZAyb9at1bYvJ4/eTUipU5aki45xw==", "dev": true, - "license": "MIT", "dependencies": { "merge-options": "^3.0.4" }, @@ -2756,6 +2727,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/whatwg-mimetype": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/whatwg-mimetype/-/whatwg-mimetype-3.0.2.tgz", + "integrity": "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/@types/which/-/which-2.0.2.tgz", @@ -4081,13 +4059,6 @@ "dev": true, "license": "MIT" }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true, - "license": "MIT" - }, "node_modules/b4a": { "version": "1.7.3", "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.3.tgz", @@ -5010,19 +4981,6 @@ "dev": true, "license": "MIT" }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/commander": { "version": "12.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", @@ -5147,13 +5105,12 @@ } }, "node_modules/coveralls-next": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/coveralls-next/-/coveralls-next-4.2.2.tgz", - "integrity": "sha512-Tw1TKXV0+aEfOgRYBN97RtEZlrLxBiZKFkngsupONkJwy0uYQNbB6VfAEnGnOUa5WkW5sBhjGB2tWha6ULrYkw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/coveralls-next/-/coveralls-next-5.0.0.tgz", + "integrity": "sha512-RCj6Oflf6iQtN3Q5b0SSemEbQBzeBjQlLUrc3bfNECTy83hMJA9krdNZ5GTRm7Jpbyo92yKUbQDP5FYlWcL5sA==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "form-data": "4.0.4", "js-yaml": "4.1.0", "lcov-parse": "1.0.0", "log-driver": "1.2.7", @@ -5166,6 +5123,19 @@ "node": ">=18" } }, + "node_modules/coveralls-next/node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/crc-32": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", @@ -5382,16 +5352,6 @@ "node": ">= 14" } }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -5911,22 +5871,6 @@ "node": ">= 0.4" } }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/es6-error": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", @@ -6781,23 +6725,6 @@ "node": ">=8.0.0" } }, - "node_modules/form-data": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", - "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", - "dev": true, - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/formatio": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/formatio/-/formatio-1.2.0.tgz", @@ -7238,19 +7165,37 @@ "license": "MIT" }, "node_modules/happy-dom": { - "version": "16.8.1", - "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-16.8.1.tgz", - "integrity": "sha512-n0QrmT9lD81rbpKsyhnlz3DgnMZlaOkJPpgi746doA+HvaMC79bdWkwjrNnGJRvDrWTI8iOcJiVTJ5CdT/AZRw==", + "version": "20.0.11", + "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-20.0.11.tgz", + "integrity": "sha512-QsCdAUHAmiDeKeaNojb1OHOPF7NjcWPBR7obdu3NwH2a/oyQaLg5d0aaCy/9My6CdPChYF07dvz5chaXBGaD4g==", "dev": true, "license": "MIT", "dependencies": { - "webidl-conversions": "^7.0.0", + "@types/node": "^20.0.0", + "@types/whatwg-mimetype": "^3.0.2", "whatwg-mimetype": "^3.0.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, + "node_modules/happy-dom/node_modules/@types/node": { + "version": "20.19.27", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.27.tgz", + "integrity": "sha512-N2clP5pJhB2YnZJ3PIHFk5RkygRX5WO/5f0WC08tp0wd+sv0rsJk3MqWn3CbNmT2J505a5336jaQj4ph1AdMug==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/happy-dom/node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -8352,9 +8297,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, "license": "MIT", "dependencies": { @@ -11546,7 +11491,7 @@ "@babel/code-frame": "^7.5.5", "jest-worker": "^24.9.0", "rollup-pluginutils": "^2.8.2", - "serialize-javascript": "^4.0.0", + "serialize-javascript": "^6.0.2", "terser": "^4.6.2" }, "peerDependencies": { @@ -11584,16 +11529,6 @@ "node": ">= 6" } }, - "node_modules/rollup-plugin-terser/node_modules/serialize-javascript": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", - "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "randombytes": "^2.1.0" - } - }, "node_modules/rollup-plugin-terser/node_modules/supports-color": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", @@ -12478,13 +12413,6 @@ "node": ">= 10.x" } }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true, - "license": "BSD-3-Clause" - }, "node_modules/stack-utils": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", @@ -14068,16 +13996,6 @@ "dev": true, "license": "MIT" }, - "node_modules/webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - } - }, "node_modules/webpack": { "version": "5.104.1", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.104.1.tgz", diff --git a/package.json b/package.json index f667384cf..ed4df2062 100644 --- a/package.json +++ b/package.json @@ -117,12 +117,12 @@ "@typescript-eslint/parser": "^5.33.0", "@vitest/browser": "3.2.4", "chai": "^4.2.0", - "coveralls-next": "^4.2.0", + "coveralls-next": "^5.0.0", "eslint": "^8.21.0", "eslint-config-prettier": "^6.10.0", "eslint-plugin-local-rules": "^3.0.2", "eslint-plugin-prettier": "^3.1.2", - "happy-dom": "^16.6.0", + "happy-dom": "^20.0.11", "jiti": "^2.4.1", "karma": "^6.4.0", "karma-browserstack-launcher": "^1.5.1", @@ -149,8 +149,8 @@ "typescript": "^4.7.4", "vite": "^6.4.1", "vitest": "^3.2.4", - "webdriverio": "^9.21.0", - "webpack": "^5.74.0" + "webpack": "^5.94.0", + "webdriverio": "^9.21.0" }, "peerDependencies": { "@react-native-async-storage/async-storage": ">=1.0.0 <3.0.0", From 8dbfb7e8efa0ea5cb3c1aebbebd50c87c217a1aa Mon Sep 17 00:00:00 2001 From: Raju Ahmed Date: Tue, 23 Dec 2025 21:33:10 +0600 Subject: [PATCH 3/3] [FSSDK-12089] add setup for umd testing --- .gitignore | 2 +- lib/index.umd.spec.ts | 23 +++ package.json | 5 +- scripts/run-umd-tests.js | 294 ++++++++++++++++++++++++++++++++++++++ vitest.browser.config.mts | 6 +- vitest.config.mts | 1 + vitest.umd.config.mts | 169 ++++++++++++++++++++++ vitest/umd-plugin.ts | 65 +++++++++ 8 files changed, 560 insertions(+), 5 deletions(-) create mode 100644 lib/index.umd.spec.ts create mode 100755 scripts/run-umd-tests.js create mode 100644 vitest.umd.config.mts create mode 100644 vitest/umd-plugin.ts diff --git a/.gitignore b/.gitignore index 19d04cedd..71c227dbc 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,4 @@ local.log **/*.gen.ts -.env \ No newline at end of file +.env diff --git a/lib/index.umd.spec.ts b/lib/index.umd.spec.ts new file mode 100644 index 000000000..c73d99144 --- /dev/null +++ b/lib/index.umd.spec.ts @@ -0,0 +1,23 @@ +import { expect, describe, it } from 'vitest'; + +import * as optimizely from './index.browser'; + +type OptimizelySdk = typeof optimizely; + +declare global { + interface Window { + optimizelySdk: OptimizelySdk; + } +} + +describe('UMD Bundle', () => { + // these are just intial tests to check the UMD bundle is loaded correctly + // we will add more comprehensive umd tests later + it('should have optimizelySdk on the window object', () => { + expect(window.optimizelySdk).toBeDefined(); + }); + + it('should export createInstance function', () => { + expect(typeof window.optimizelySdk.createInstance).toBe('function'); + }); +}); diff --git a/package.json b/package.json index ed4df2062..9ea6a2e21 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,9 @@ "test-browser": "node ./scripts/run-browser-tests.js", "test-browser-local": "USE_LOCAL_BROWSER=true node ./scripts/run-browser-tests.js", "test-browser-browserstack": "USE_LOCAL_BROWSER=false node ./scripts/run-browser-tests.js", + "test-umd": "node ./scripts/run-umd-tests.js", + "test-umd-local": "USE_LOCAL_BROWSER=true node ./scripts/run-umd-tests.js", + "test-umd-browserstack": "USE_LOCAL_BROWSER=false node ./scripts/run-umd-tests.js", "test-mocha": "TS_NODE_COMPILER_OPTIONS='{\"module\": \"commonjs\" }' mocha -r ts-node/register -r tsconfig-paths/register -r lib/tests/exit_on_unhandled_rejection.js 'lib/**/*.tests.ts' 'lib/**/*.tests.js'", "test": "npm run test-mocha && npm run test-vitest", "posttest": "npm run lint", @@ -74,7 +77,7 @@ "prebuild": "npm run clean", "build": "npm run validate-platform-isolation && tsc --noEmit && npm run genmsg && rollup -c && cp dist/index.browser.d.ts dist/index.d.ts", "build:win": "tsc --noEmit && npm run genmsg && rollup -c && type nul > dist/optimizely.lite.es.d.ts && type nul > dist/optimizely.lite.es.min.d.ts && type nul > dist/optimizely.lite.min.d.ts", - "build-browser-umd": "rollup -c --config-umd", + "build-browser-umd": "npm run validate-platform-isolation && tsc --noEmit && npm run genmsg && rollup -c --config-umd", "coveralls": "nyc --reporter=lcov npm test", "prepare": "npm run build", "prepublishOnly": "npm test", diff --git a/scripts/run-umd-tests.js b/scripts/run-umd-tests.js new file mode 100755 index 000000000..9aafa9fe8 --- /dev/null +++ b/scripts/run-umd-tests.js @@ -0,0 +1,294 @@ +#!/usr/bin/env node + +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +const { execSync } = require('child_process'); +const browserstack = require('browserstack-local'); +const fs = require('fs'); +const path = require('path'); + +// Browser configurations grouped by browser name +const BROWSER_CONFIGS = { + chrome: [ + { name: 'chrome-102-windows', browserVersion: '102', os: 'Windows', osVersion: '11' }, + { name: 'chrome-latest-windows', browserVersion: 'latest', os: 'Windows', osVersion: '11' }, + ], + firefox: [ + { name: 'firefox-91-windows', browserVersion: '91', os: 'Windows', osVersion: '11' }, + { name: 'firefox-latest-windows', browserVersion: 'latest', os: 'Windows', osVersion: '11' }, + ], + edge: [ + { name: 'edge-89-windows', browserVersion: '89', os: 'Windows', osVersion: '11' }, + { name: 'edge-latest-windows', browserVersion: 'latest', os: 'Windows', osVersion: '11' }, + ], + safari: [ + { name: 'safari-monterey', os: 'OS X', osVersion: 'Monterey' }, + { name: 'safari-sequoia', os: 'OS X', osVersion: 'Sequoia' }, + ] +}; + +// Determine if we should use local browser or BrowserStack +// Priority: USE_LOCAL_BROWSER env var, then check for BrowserStack credentials +let useLocalBrowser = process.env.USE_LOCAL_BROWSER === 'true'; + +if (!useLocalBrowser) { + // Check for BrowserStack credentials + const username = process.env.BROWSERSTACK_USERNAME || process.env.BROWSER_STACK_USERNAME; + const accessKey = process.env.BROWSERSTACK_ACCESS_KEY || process.env.BROWSER_STACK_ACCESS_KEY; + + console.log('\n' + '='.repeat(80)); + console.log('BrowserStack Credentials Check:'); + console.log('='.repeat(80)); + console.log(`BROWSERSTACK_USERNAME: ${username ? '✓ Available' : '✗ Not found'}`); + console.log(`BROWSERSTACK_ACCESS_KEY: ${accessKey ? '✓ Available' : '✗ Not found'}`); + console.log('='.repeat(80) + '\n'); + + if (!username || !accessKey) { + console.log('BrowserStack credentials not found - falling back to local browser mode'); + useLocalBrowser = true; + } +} + + +let bs_local = null; + +function startTunnel(localIdentifier) { + const accessKey = process.env.BROWSERSTACK_ACCESS_KEY || process.env.BROWSER_STACK_ACCESS_KEY; + + console.log(`Starting BrowserStack Local tunnel with identifier: ${localIdentifier}...`); + bs_local = new browserstack.Local(); + const bsLocalArgs = { + key: accessKey, + force: true, + forceLocal: true, + // Enable verbose logging to debug tunnel issues + verbose: true, + // Use the provided identifier for parallel tunnel support + localIdentifier: localIdentifier, + }; + + return new Promise((resolve, reject) => { + bs_local.start(bsLocalArgs, (error) => { + if (error) { + console.error('Error starting BrowserStack Local:', error); + reject(error); + } else { + console.log('BrowserStack Local tunnel started successfully'); + console.log(`BrowserStack Local PID: ${bs_local.pid}`); + console.log(`Local Identifier: ${localIdentifier}`); + // Wait longer for tunnel to fully establish and register with BrowserStack + console.log('Waiting for tunnel to establish...'); + setTimeout(() => { + console.log('Tunnel ready!'); + resolve(); + }, 10000); + } + }); + }); +} + +function stopTunnel() { + if (!bs_local) { + return Promise.resolve(); + } + + return new Promise((resolve) => { + bs_local.stop(() => { + console.log('BrowserStack Local tunnel stopped'); + resolve(); + }); + }); +} + +async function runTests() { + let exitCode = 0; + + try { + // Step 1: Run npm run build + console.log('\n' + '='.repeat(80)); + console.log('Building project...'); + console.log('='.repeat(80)); + try { + execSync('npm run build-browser-umd', { stdio: 'inherit' }); + console.log('Build completed successfully!'); + } catch (error) { + console.error('Failed to build project:', error.message); + exitCode = 1; + return; + } + + // Step 2: Copy the UMD file to vitest/public/dist/ + console.log('\n' + '='.repeat(80)); + console.log('Copying UMD file to vitest/public/dist/...'); + console.log('='.repeat(80)); + try { + const sourceFile = path.join(process.cwd(), 'dist/optimizely.browser.umd.min.js'); + const destDir = path.join(process.cwd(), 'vitest/public/dist'); + const destFile = path.join(destDir, 'optimizely.browser.umd.min.js'); + + // Create destination directory if it doesn't exist + if (!fs.existsSync(destDir)) { + fs.mkdirSync(destDir, { recursive: true }); + console.log(`Created directory: ${destDir}`); + } + + // Copy the file + fs.copyFileSync(sourceFile, destFile); + console.log(`Copied ${sourceFile} to ${destFile}`); + } catch (error) { + console.error('Failed to copy UMD file:', error.message); + exitCode = 1; + return; + } + + // Patch Vitest viewport command to prevent WebDriver Bidi errors + console.log('\n' + '='.repeat(80)); + console.log('Patching Vitest viewport command...'); + console.log('='.repeat(80)); + try { + execSync('node ./scripts/patch-vitest-viewport.js', { stdio: 'inherit' }); + } catch (error) { + console.error('Failed to patch Vitest viewport command:', error.message); + exitCode = 1; + return; + } + + // Get browser name from environment variable (default to chrome) + const browserName = (process.env.TEST_BROWSER || 'chrome').toLowerCase(); + + let configs; + + if (useLocalBrowser) { + configs = [{ + name: `${browserName}`, + }]; + console.log('Local browser mode: using local browser installation'); + } else { + // For BrowserStack, use the defined configs + configs = BROWSER_CONFIGS[browserName]; + if (!configs || configs.length === 0) { + console.error(`Error: No configurations found for browser '${browserName}'`); + console.error(`Available browsers: ${Object.keys(BROWSER_CONFIGS).join(', ')}`); + exitCode = 1; + return; + } + } + + // Only start tunnel if using BrowserStack + let localIdentifier; + if (!useLocalBrowser) { + // Generate a random identifier for parallel tunnel support (100000-900000) + localIdentifier = Math.floor(Math.random() * 800000) + 100000; + localIdentifier = localIdentifier.toString(); + await startTunnel(localIdentifier); + } else { + console.log('Using local browser mode - no BrowserStack connection needed'); + } + + console.log('\n' + '='.repeat(80)); + console.log(`Running UMD tests for browser: ${browserName}`); + console.log(`Total configurations: ${configs.length}`); + console.log('='.repeat(80) + '\n'); + + const results = []; + + // Run each config serially + for (const config of configs) { + console.log(`\n${'='.repeat(80)}`); + console.log(`Running: ${config.name}`); + console.log(`Browser: ${browserName}${config.browserVersion ? ` ${config.browserVersion}` : ''}`); + console.log(`OS: ${config.os} ${config.osVersion}`); + console.log('='.repeat(80)); + + // Set environment variables for this config + const env = { + ...process.env, + TEST_BROWSER: browserName, + TEST_BROWSER_VERSION: config.browserVersion, + TEST_OS_NAME: config.os, + TEST_OS_VERSION: config.osVersion, + // Pass the local identifier to vitest config for BrowserStack capabilities + BROWSERSTACK_LOCAL_IDENTIFIER: localIdentifier, + }; + + + try { + console.log('Starting vitest UMD test...'); + // Run vitest with the UMD config + execSync('npm run test-vitest -- --config vitest.umd.config.mts', { + stdio: 'inherit', + env, + }); + + console.log(`\n✓ ${config.name} passed!`); + results.push({ config: config.name, success: true }); + } catch (error) { + console.error(`\n✗ ${config.name} failed`); + if (error.message) { + console.error('Error message:', error.message); + } + results.push({ config: config.name, success: false }); + } + } + + // Print summary + console.log('\n' + '='.repeat(80)); + console.log(`UMD test summary for ${browserName}:`); + console.log('='.repeat(80)); + + const failures = []; + const successes = []; + + results.forEach(({ config, success }) => { + if (success) { + successes.push(config); + console.log(`✓ ${config}: PASSED`); + } else { + failures.push(config); + console.error(`✗ ${config}: FAILED`); + } + }); + + console.log('='.repeat(80)); + console.log(`Total: ${results.length} configurations`); + console.log(`Passed: ${successes.length}`); + console.log(`Failed: ${failures.length}`); + console.log('='.repeat(80)); + + // Set exit code based on results + if (failures.length > 0) { + console.error(`\nSome ${browserName} configurations failed. See above for details.`); + exitCode = 1; + } else { + console.log(`\nAll ${browserName} configurations passed!`); + exitCode = 0; + } + } finally { + // Only stop tunnel if using BrowserStack + if (!useLocalBrowser) { + await stopTunnel(); + } + + // Exit after tunnel is properly closed + process.exit(exitCode); + } +} + +// Run the tests +runTests().catch((error) => { + console.error('Fatal error:', error); + process.exit(1); +}); diff --git a/vitest.browser.config.mts b/vitest.browser.config.mts index f5c20e13c..7bc7d43c4 100644 --- a/vitest.browser.config.mts +++ b/vitest.browser.config.mts @@ -42,7 +42,7 @@ const testOsVersion = process.env.TEST_OS_VERSION; // Build local browser capabilities function buildLocalCapabilities() { return { - testBrowser, + browserName: testBrowser, 'goog:chromeOptions': { args: [ '--disable-blink-features=AutomationControlled', @@ -149,8 +149,7 @@ export default defineConfig({ test: { isolate: false, fileParallelism: true, - // Reduce concurrency for BrowserStack to minimize tunnel load and WebSocket connection issues - maxConcurrency: useLocalBrowser ? 5 : 1, + maxConcurrency: 5, onConsoleLog: () => true, browser: { enabled: true, @@ -167,6 +166,7 @@ export default defineConfig({ exclude: [ 'lib/**/*.react_native.spec.ts', 'lib/**/*.node.spec.ts', + 'lib/*.umd.spec.ts' ], typecheck: { enabled: true, diff --git a/vitest.config.mts b/vitest.config.mts index 178edb642..256405faf 100644 --- a/vitest.config.mts +++ b/vitest.config.mts @@ -27,6 +27,7 @@ export default defineConfig({ onConsoleLog: () => true, environment: 'happy-dom', include: ['lib/**/*.spec.ts'], + exclude: ['lib/*.umd.spec.ts'], typecheck: { enabled: true, tsconfig: 'tsconfig.spec.json', diff --git a/vitest.umd.config.mts b/vitest.umd.config.mts new file mode 100644 index 000000000..de9090d02 --- /dev/null +++ b/vitest.umd.config.mts @@ -0,0 +1,169 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/// +import path from 'path'; +import { defineConfig } from 'vitest/config' +import { umdPlugin } from './vitest/umd-plugin'; + +// Check if we should use local browser instead of BrowserStack +const useLocalBrowser = process.env.USE_LOCAL_BROWSER === 'true'; + + +// Get browser configuration from TEST_* environment variables +const testBrowser = process.env.TEST_BROWSER || 'chrome'; +const testBrowserVersion = process.env.TEST_BROWSER_VERSION; +const testOsName = process.env.TEST_OS_NAME; +const testOsVersion = process.env.TEST_OS_VERSION; + +// const browserConfig = { +// name: testBrowser, +// browserName: testBrowser, +// browserVersion: testBrowserVersion, +// os: testOsName, +// osVersion: testOsVersion, +// }; + +// const browserConfigs = [browserConfig]; + +// Build local browser capabilities +function buildLocalCapabilities() { + return { + browserName: testBrowser, + 'goog:chromeOptions': { + args: [ + '--disable-blink-features=AutomationControlled', + '--disable-dev-shm-usage', + '--no-sandbox', + ], + }, + }; +} + +// Build BrowserStack capabilities +function buildBrowserStackCapabilities() { + const localIdentifier = process.env.BROWSERSTACK_LOCAL_IDENTIFIER; + + return { + browserName: testBrowser, + 'wdio:enforceWebDriverClassic': true, // this doesn't work due to vitest bug, still keeping here for future reference + 'goog:chromeOptions': { + args: [ + '--disable-blink-features=AutomationControlled', + '--disable-dev-shm-usage', + '--no-sandbox', + ], + }, + 'bstack:options': { + os: testOsName, + osVersion: testOsVersion, + browserVersion: testBrowserVersion, + buildName: process.env.VITEST_BUILD_NAME || 'Vitest Browser Tests', + projectName: 'Optimizely JavaScript SDK', + sessionName: `${testBrowser} ${testBrowserVersion || ''} on ${testOsName} ${testOsVersion}`, + local: true, + // Include localIdentifier for parallel tunnel support + ...(localIdentifier && { localIdentifier }), + debug: false, + networkLogs: false, + consoleLogs: 'errors' as const, + seleniumLogs: false, + idleTimeout: 900, // 15 minutes idle timeout - prevents premature session closure during long test runs + }, + }; +} + +function buildBrowserInstances() { + if (useLocalBrowser) { + // Local browser configurations - all browsers + return [{ + browser: testBrowser, + capabilities: buildLocalCapabilities(), + }]; + } else { + + const username = process.env.BROWSERSTACK_USERNAME || process.env.BROWSER_STACK_USERNAME; + const key = process.env.BROWSERSTACK_ACCESS_KEY || process.env.BROWSER_STACK_ACCESS_KEY; + + return [{ + browser: testBrowser, + user: username, + key: key, + capabilities: buildBrowserStackCapabilities(), + connectionRetryTimeout: 120000, // 2 minutes connection retry timeout + connectionRetryCount: 3, // Retry 3 times on connection failure + waitforTimeout: 30000, // 30 seconds wait timeout - matches test expectations + waitforInterval: 1000, // Poll every 1 second - faster feedback + keepAlive: true, + keepAliveInterval: 30000, + }]; + } +} + +export default defineConfig({ + plugins: [ + umdPlugin(), + ], + resolve: { + alias: { + 'error_message': path.resolve(__dirname, './lib/message/error_message'), + 'log_message': path.resolve(__dirname, './lib/message/log_message'), + }, + }, + esbuild: { + target: 'es2015', + format: 'esm', + }, + build: { + target: 'es2015', + }, + optimizeDeps: { + // Force chai to be pre-bundled with ES6 target to remove class static blocks + // This avoids issues with browsers that do not support class static blocks like firefox 91 + include: ['chai'], + esbuildOptions: { + target: 'es2015', + }, + }, + // Serve public files from vitest/public directory + publicDir: 'vitest/public', + server: { + host: '0.0.0.0', + // for safari, browserstack redirects localhost to bs-local.com + allowedHosts: ['bs-local.com', 'localhost'], + }, + test: { + isolate: false, + fileParallelism: true, + maxConcurrency: 5, + onConsoleLog: () => true, + browser: { + enabled: true, + provider: 'webdriverio', + headless: false, + instances: buildBrowserInstances(), + connectTimeout: 300000, + }, + testTimeout: 60000, + hookTimeout: 30000, + include: [ + 'lib/**/*.umd.spec.ts', + ], + typecheck: { + enabled: true, + tsconfig: 'tsconfig.spec.json', + }, + }, +}); diff --git a/vitest/umd-plugin.ts b/vitest/umd-plugin.ts new file mode 100644 index 000000000..9c285b835 --- /dev/null +++ b/vitest/umd-plugin.ts @@ -0,0 +1,65 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import type { Plugin } from 'vitest/config'; +import type { IncomingMessage, ServerResponse } from 'http'; + +export function umdPlugin(): Plugin { + return { + name: 'umd-plugin', + enforce: 'pre' as const, + configureServer(server) { + // Add middleware to inject umd script into HTML responses + server.middlewares.use((_req: IncomingMessage, res: ServerResponse, next: () => void) => { + const originalWrite = res.write; + const originalEnd = res.end; + const chunks: any[] = []; + + // @ts-ignore + res.write = function(chunk: any, ..._args: any[]) { + chunks.push(Buffer.from(chunk)); + return true; + }; + + // @ts-ignore + res.end = function(chunk: any, ...args: any[]) { + if (chunk) { + chunks.push(Buffer.from(chunk)); + } + + const buffer = Buffer.concat(chunks); + let body = buffer.toString('utf8'); + + // Inject console-capture script into HTML responses + if (res.getHeader('content-type')?.toString().includes('text/html')) { + const scriptTag = ''; + if (body.includes('')) { + body = body.replace('', `${scriptTag}\n`); + res.setHeader('content-length', Buffer.byteLength(body)); + } + } + + // Restore original methods and send response + res.write = originalWrite; + res.end = originalEnd; + res.end(body, ...args); + }; + + next(); + }); + + }, + }; +}