From 76867753e05f60ad264707dc092c362d8dfc7287 Mon Sep 17 00:00:00 2001 From: lenasano Date: Sun, 8 Oct 2023 15:48:22 -0700 Subject: [PATCH 1/7] use node.js singleton pattern fetch automation network conditions from feature flag --- .../multipage-testing-example/automation.js | 15 +++++- .../client/index-off.js | 6 ++- .../client/index-on.js | 6 ++- .../multipage-testing-example/server/index.js | 15 +++--- .../multipage-testing-example/server/split.js | 29 +++++++++++ .../server/xxxapp.js | 48 +++++++++++++++++++ 6 files changed, 106 insertions(+), 13 deletions(-) create mode 100644 advanced/multipage-testing-example/server/split.js create mode 100644 advanced/multipage-testing-example/server/xxxapp.js diff --git a/advanced/multipage-testing-example/automation.js b/advanced/multipage-testing-example/automation.js index 8ff5e9f..8f31af4 100644 --- a/advanced/multipage-testing-example/automation.js +++ b/advanced/multipage-testing-example/automation.js @@ -1,6 +1,10 @@ +require('dotenv').config(); const puppeteer = require('puppeteer'); -const SAMPLE_SIZE = 500; +const Split = require('./server/split.js'); +const splitClient = Split.getInstance().client; + +const SAMPLE_SIZE = 200; // https://fdalvi.github.io/blog/2018-02-05-puppeteer-network-throttle/ const NETWORK_CONDITIONS = { @@ -27,6 +31,7 @@ const NETWORK_CONDITIONS = { } }; + (async () => { console.log('Navigation script'); @@ -41,7 +46,13 @@ const NETWORK_CONDITIONS = { // Emulate network conditions and disable cache to simulate new users await page.setCacheEnabled(false); - await page.emulateNetworkConditions(NETWORK_CONDITIONS.Good3G); + + // Evaluate Split flag to determine what the emulated network conditions should be for this user + const networkSpeed = splitClient.getTreatment(id, process.env.FEATURE_FLAG_SPEED_NAME); + + (networkSpeed in NETWORK_CONDITIONS) ? + await page.emulateNetworkConditions(NETWORK_CONDITIONS[networkSpeed]) : + await page.emulateNetworkConditions(NETWORK_CONDITIONS.Good3G); // Navigate to URL await page.goto(`http://localhost:3000/?id=${id}`); diff --git a/advanced/multipage-testing-example/client/index-off.js b/advanced/multipage-testing-example/client/index-off.js index e8ce2b0..e8869ad 100755 --- a/advanced/multipage-testing-example/client/index-off.js +++ b/advanced/multipage-testing-example/client/index-off.js @@ -2,7 +2,11 @@ import { SplitRumAgent, webVitals } from '@splitsoftware/browser-rum-agent'; SplitRumAgent - .setup(process.env.CLIENT_SIDE_SDK_KEY) + .setup(process.env.CLIENT_SIDE_SDK_KEY, + { + pushRate: 2 // seconds - post queued event data to Split backend every 2 seconds + } + ) .addIdentities([ // get key from URL query parameter `id` { key: new URLSearchParams(window.location.search).get('id'), trafficType: 'user' } diff --git a/advanced/multipage-testing-example/client/index-on.js b/advanced/multipage-testing-example/client/index-on.js index cc5a4df..ba838ae 100755 --- a/advanced/multipage-testing-example/client/index-on.js +++ b/advanced/multipage-testing-example/client/index-on.js @@ -2,7 +2,11 @@ import('./browser-rum-agent').then(({ SplitRumAgent, webVitals }) => { SplitRumAgent - .setup(process.env.CLIENT_SIDE_SDK_KEY) + .setup(process.env.CLIENT_SIDE_SDK_KEY, + { + pushRate: 2 // seconds - post queued event data to Split backend every 2 seconds + } + ) .addIdentities([ // get key from URL query parameter `id` { key: new URLSearchParams(window.location.search).get('id'), trafficType: 'user' } diff --git a/advanced/multipage-testing-example/server/index.js b/advanced/multipage-testing-example/server/index.js index 4c36184..5438cab 100644 --- a/advanced/multipage-testing-example/server/index.js +++ b/advanced/multipage-testing-example/server/index.js @@ -3,23 +3,20 @@ const path = require('path'); const express = require('express'); const app = express(); -const { SplitFactory } = require('@splitsoftware/splitio'); - -const client = SplitFactory({ - core: { - authorizationKey: process.env.SERVER_SIDE_SDK_KEY, - }, - debug: 'ERROR' -}).client(); +const Split = require('./split.js'); +const splitClient = Split.getInstance().client; // Split traffic to serve two variants of the Web page, using `id` query param as user key for feature flag evaluations // Web page variants are located at different folders: `dist/off` ('off' treatment) and `dist/on` ('on' treatment) + app.use('/on', express.static(path.join(__dirname, '..', 'dist', 'on'))); app.use('/off', express.static(path.join(__dirname, '..', 'dist', 'off'))); app.use('/', (req, res, next) => { if (req.query.id) { - const treatment = client.getTreatment(req.query.id, process.env.FEATURE_FLAG_NAME); + + const treatment = splitClient.getTreatment(req.query.id, process.env.FEATURE_FLAG_ASYNC_NAME); console.log('serving treatment ' + treatment); + if (treatment === 'on') { return res.redirect('/on' + req.url) } else { diff --git a/advanced/multipage-testing-example/server/split.js b/advanced/multipage-testing-example/server/split.js new file mode 100644 index 0000000..26999ac --- /dev/null +++ b/advanced/multipage-testing-example/server/split.js @@ -0,0 +1,29 @@ +require('dotenv').config(); +const { SplitFactory } = require('@splitsoftware/splitio'); + +class PrivateSplit { + constructor() { + this.client = SplitFactory({ + core: { + authorizationKey: process.env.SERVER_SIDE_SDK_KEY, + }, + scheduler: { + impressionsRefreshRate: 2 // s - send information on who got what treatment at + }, // what time back to Split server every 2 seconds + debug: 'INFO' + }).client(); + } +} +class Split { + constructor() { + throw new Error('Use Split.getInstance()'); + } + static getInstance() { + if (!Split.instance) { + Split.instance = new PrivateSplit(); + } + return Split.instance; + } +} + +module.exports = Split; \ No newline at end of file diff --git a/advanced/multipage-testing-example/server/xxxapp.js b/advanced/multipage-testing-example/server/xxxapp.js new file mode 100644 index 0000000..d053162 --- /dev/null +++ b/advanced/multipage-testing-example/server/xxxapp.js @@ -0,0 +1,48 @@ +const path = require('path'); +const express = require('express'); +const appRouter = express.Router(); + +const { SplitFactory } = require('@splitsoftware/splitio'); +require('dotenv').config(); + +const client = SplitFactory({ + core: { + authorizationKey: process.env.SERVER_SIDE_SDK_KEY + }, + scheduler: { + impressionsRefreshRate: 5 // seconds - send information on who got what treatment at what time back to Split server every 5 seconds + }, + debug: 'INFO' +}).client(); + +// Split traffic to serve two variants of the Web page, using `id` query param as user key for feature flag evaluations +// Web page variants are located at different folders: `dist/sync` (OFF treatment) and `dist/async` (ON treatment) + +// Split traffic to serve assets from two different folders, `dist/sync` (OFF treatment) and `dist/async` (ON treatment), using id from query parameters +appRouter.use((req, res, next) => { + const treatment = client.getTreatment(req.query.id, process.env.FEATURE_FLAG_NAME); + console.log(`serving ${treatment}`); + if (req.query.id && treatment === 'on') { + req.url = '/on' + req.url; + } else { + req.url = '/off' + req.url; + } + + next(); +}); + +appRouter.use('/on', express.static(path.join(__dirname, '..', 'dist', 'async'))); +appRouter.use('/off', express.static(path.join(__dirname, '..', 'dist', 'sync'))); + +/* is the client ready to evaluate treatments yet? +var congratsMessage = { + "eng": "success", + "pol": "udało się", + "chs": "成功了", + "control": "warning: feature flag not evaluated" +}; +const language = client.getTreatment(req.query.id, "language"); +if (language in congratsMessage) + console.log( congratsMessage[ language ] );*/ + +module.exports = appRouter; From 5d272798d857da13601319e1c9aee04ad9ca9097 Mon Sep 17 00:00:00 2001 From: lenasano Date: Wed, 11 Oct 2023 04:13:11 -0700 Subject: [PATCH 2/7] add navigation to automation script so that CLS and INP are measured --- advanced/multipage-testing-example/automation.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/advanced/multipage-testing-example/automation.js b/advanced/multipage-testing-example/automation.js index 8f31af4..b90c248 100644 --- a/advanced/multipage-testing-example/automation.js +++ b/advanced/multipage-testing-example/automation.js @@ -4,7 +4,7 @@ const puppeteer = require('puppeteer'); const Split = require('./server/split.js'); const splitClient = Split.getInstance().client; -const SAMPLE_SIZE = 200; +const SAMPLE_SIZE = 500; // https://fdalvi.github.io/blog/2018-02-05-puppeteer-network-throttle/ const NETWORK_CONDITIONS = { @@ -62,8 +62,17 @@ const NETWORK_CONDITIONS = { // Wait some time await new Promise(resolve => setTimeout(resolve, 1000)); + + // Switch to a new tab so that the Web Vitals Interaction to Next Paint (INP) + // and Cumulative Layout Shift (CLS) measurements will be sent + await page.goto('about:blank'); + + await page.close(); } + // Wait some time + await new Promise(resolve => setTimeout(resolve, 1000)); + // Close browser await browser.close(); From 30b42939fd476bdd299b6d8f2fcd17c30d614dca Mon Sep 17 00:00:00 2001 From: lenasano Date: Wed, 11 Oct 2023 17:50:56 -0700 Subject: [PATCH 3/7] allow feature flag to determine imgur image size --- advanced/multipage-testing-example/client/index.html | 12 +++++++++++- advanced/multipage-testing-example/server/index.js | 10 +++++----- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/advanced/multipage-testing-example/client/index.html b/advanced/multipage-testing-example/client/index.html index b98792d..6355eef 100644 --- a/advanced/multipage-testing-example/client/index.html +++ b/advanced/multipage-testing-example/client/index.html @@ -13,6 +13,16 @@ w.addEventListener('unhandledrejection', g.l2); }(window)) + @@ -36,7 +46,7 @@

<%= htmlWebpackPlugin.options.title %>

Pharetra convallis posuere morbi leo urna molestie. Duis ultricies lacus sed turpis tincidunt id aliquet. Bibendum enim facilisis gravida neque. Gravida quis blandit turpis cursus in hac habitasse platea.

- +
diff --git a/advanced/multipage-testing-example/server/index.js b/advanced/multipage-testing-example/server/index.js index 5438cab..e8165f1 100644 --- a/advanced/multipage-testing-example/server/index.js +++ b/advanced/multipage-testing-example/server/index.js @@ -14,13 +14,13 @@ app.use('/off', express.static(path.join(__dirname, '..', 'dist', 'off'))); app.use('/', (req, res, next) => { if (req.query.id) { - const treatment = splitClient.getTreatment(req.query.id, process.env.FEATURE_FLAG_ASYNC_NAME); - console.log('serving treatment ' + treatment); + const optimizeAsync = splitClient.getTreatment(req.query.id, process.env.FEATURE_FLAG_ASYNC_NAME); + const imageSize = splitClient.getTreatment(req.query.id, process.env.FEATURE_IMAGE_SIZE_NAME); - if (treatment === 'on') { - return res.redirect('/on' + req.url) + if (optimizeAsync === 'on') { + return res.redirect('/on' + req.url + '&img=' + imageSize) } else { - return res.redirect('/off' + req.url); + return res.redirect('/off' + req.url + '&img=' + imageSize); } } From 7251c9ba01613ec085d00e2e111e8b66b1b69c34 Mon Sep 17 00:00:00 2001 From: lenasano Date: Wed, 11 Oct 2023 18:35:09 -0700 Subject: [PATCH 4/7] add .env examples for two feature flag names --- .../multipage-testing-example/.env.example | 4 +- .../server/xxxapp.js | 48 ------------------- 2 files changed, 3 insertions(+), 49 deletions(-) delete mode 100644 advanced/multipage-testing-example/server/xxxapp.js diff --git a/advanced/multipage-testing-example/.env.example b/advanced/multipage-testing-example/.env.example index 08ae057..bf40221 100644 --- a/advanced/multipage-testing-example/.env.example +++ b/advanced/multipage-testing-example/.env.example @@ -1,3 +1,5 @@ -FEATURE_FLAG_NAME=YOUR_FEATURE_FLAG_NAME CLIENT_SIDE_SDK_KEY=YOUR_CLIENT_SIDE_SDK_KEY SERVER_SIDE_SDK_KEY=YOUR_SERVER_SIDE_SDK_KEY +FEATURE_FLAG_ASYNC_NAME=async_optimization +FEATURE_FLAG_SPEED_NAME=network_speed +FEATURE_IMAGE_SIZE_NAME=image_size \ No newline at end of file diff --git a/advanced/multipage-testing-example/server/xxxapp.js b/advanced/multipage-testing-example/server/xxxapp.js deleted file mode 100644 index d053162..0000000 --- a/advanced/multipage-testing-example/server/xxxapp.js +++ /dev/null @@ -1,48 +0,0 @@ -const path = require('path'); -const express = require('express'); -const appRouter = express.Router(); - -const { SplitFactory } = require('@splitsoftware/splitio'); -require('dotenv').config(); - -const client = SplitFactory({ - core: { - authorizationKey: process.env.SERVER_SIDE_SDK_KEY - }, - scheduler: { - impressionsRefreshRate: 5 // seconds - send information on who got what treatment at what time back to Split server every 5 seconds - }, - debug: 'INFO' -}).client(); - -// Split traffic to serve two variants of the Web page, using `id` query param as user key for feature flag evaluations -// Web page variants are located at different folders: `dist/sync` (OFF treatment) and `dist/async` (ON treatment) - -// Split traffic to serve assets from two different folders, `dist/sync` (OFF treatment) and `dist/async` (ON treatment), using id from query parameters -appRouter.use((req, res, next) => { - const treatment = client.getTreatment(req.query.id, process.env.FEATURE_FLAG_NAME); - console.log(`serving ${treatment}`); - if (req.query.id && treatment === 'on') { - req.url = '/on' + req.url; - } else { - req.url = '/off' + req.url; - } - - next(); -}); - -appRouter.use('/on', express.static(path.join(__dirname, '..', 'dist', 'async'))); -appRouter.use('/off', express.static(path.join(__dirname, '..', 'dist', 'sync'))); - -/* is the client ready to evaluate treatments yet? -var congratsMessage = { - "eng": "success", - "pol": "udało się", - "chs": "成功了", - "control": "warning: feature flag not evaluated" -}; -const language = client.getTreatment(req.query.id, "language"); -if (language in congratsMessage) - console.log( congratsMessage[ language ] );*/ - -module.exports = appRouter; From 1625e20e249ad9a565c42a0c76a016da41d89caf Mon Sep 17 00:00:00 2001 From: lenasano Date: Thu, 12 Oct 2023 04:37:56 -0700 Subject: [PATCH 5/7] launch puppeteer in headful mode and increase wait time for accurate CLS measurements --- advanced/multipage-testing-example/automation.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/advanced/multipage-testing-example/automation.js b/advanced/multipage-testing-example/automation.js index b90c248..c1fe965 100644 --- a/advanced/multipage-testing-example/automation.js +++ b/advanced/multipage-testing-example/automation.js @@ -4,7 +4,7 @@ const puppeteer = require('puppeteer'); const Split = require('./server/split.js'); const splitClient = Split.getInstance().client; -const SAMPLE_SIZE = 500; +const SAMPLE_SIZE = 5; // https://fdalvi.github.io/blog/2018-02-05-puppeteer-network-throttle/ const NETWORK_CONDITIONS = { @@ -36,7 +36,7 @@ const NETWORK_CONDITIONS = { console.log('Navigation script'); // Launch browser - const browser = await puppeteer.launch(); + const browser = await puppeteer.launch({headless: false, defaultViewport: null}); for (let id = 0; id < SAMPLE_SIZE; id++) { console.log(`Running ${id} of ${SAMPLE_SIZE}`); @@ -61,7 +61,7 @@ const NETWORK_CONDITIONS = { await page.click('#split_logo'); // Wait some time - await new Promise(resolve => setTimeout(resolve, 1000)); + await new Promise(resolve => setTimeout(resolve, 8000)); // Switch to a new tab so that the Web Vitals Interaction to Next Paint (INP) // and Cumulative Layout Shift (CLS) measurements will be sent From f571612e50550a60f7e716d7acf043a1d7f740b9 Mon Sep 17 00:00:00 2001 From: lenasano Date: Sat, 14 Oct 2023 15:17:58 -0700 Subject: [PATCH 6/7] implement code review suggestions --- .../multipage-testing-example/.env.example | 6 +-- .../multipage-testing-example/automation.js | 34 ++++++-------- .../client/index.html | 4 +- .../multipage-testing-example/server/index.js | 8 ++-- .../multipage-testing-example/server/split.js | 46 +++++++++---------- 5 files changed, 46 insertions(+), 52 deletions(-) diff --git a/advanced/multipage-testing-example/.env.example b/advanced/multipage-testing-example/.env.example index bf40221..3eed8d7 100644 --- a/advanced/multipage-testing-example/.env.example +++ b/advanced/multipage-testing-example/.env.example @@ -1,5 +1,5 @@ CLIENT_SIDE_SDK_KEY=YOUR_CLIENT_SIDE_SDK_KEY SERVER_SIDE_SDK_KEY=YOUR_SERVER_SIDE_SDK_KEY -FEATURE_FLAG_ASYNC_NAME=async_optimization -FEATURE_FLAG_SPEED_NAME=network_speed -FEATURE_IMAGE_SIZE_NAME=image_size \ No newline at end of file +FEATURE_FLAG_OPTIMIZE_PAGE=async_optimization +FEATURE_FLAG_NETWORK_SPEED=network_speed +FEATURE_FLAG_IMAGE_SIZE=image_size \ No newline at end of file diff --git a/advanced/multipage-testing-example/automation.js b/advanced/multipage-testing-example/automation.js index c1fe965..c4c19cf 100644 --- a/advanced/multipage-testing-example/automation.js +++ b/advanced/multipage-testing-example/automation.js @@ -1,10 +1,10 @@ require('dotenv').config(); const puppeteer = require('puppeteer'); -const Split = require('./server/split.js'); -const splitClient = Split.getInstance().client; +const getSplitClient = require('./server/split.js'); +const splitClient = getSplitClient(); -const SAMPLE_SIZE = 5; +const SAMPLE_SIZE = 100; // https://fdalvi.github.io/blog/2018-02-05-puppeteer-network-throttle/ const NETWORK_CONDITIONS = { @@ -48,30 +48,24 @@ const NETWORK_CONDITIONS = { await page.setCacheEnabled(false); // Evaluate Split flag to determine what the emulated network conditions should be for this user - const networkSpeed = splitClient.getTreatment(id, process.env.FEATURE_FLAG_SPEED_NAME); + const networkSpeed = splitClient.getTreatment(id, process.env.FEATURE_FLAG_NETWORK_SPEED); - (networkSpeed in NETWORK_CONDITIONS) ? - await page.emulateNetworkConditions(NETWORK_CONDITIONS[networkSpeed]) : - await page.emulateNetworkConditions(NETWORK_CONDITIONS.Good3G); + await page.emulateNetworkConditions( + (networkSpeed in NETWORK_CONDITIONS) + ? NETWORK_CONDITIONS[networkSpeed] + : NETWORK_CONDITIONS.Good3G + ); // Navigate to URL - await page.goto(`http://localhost:3000/?id=${id}`); + await page.goto(`http://localhost:3000/?id=${id}`, { waitUntil: "networkidle0" }); - // Perform click on an element - await page.click('#split_logo'); - - // Wait some time - await new Promise(resolve => setTimeout(resolve, 8000)); - - // Switch to a new tab so that the Web Vitals Interaction to Next Paint (INP) + // Close the tab so that the Web Vitals Interaction to Next Paint (INP) // and Cumulative Layout Shift (CLS) measurements will be sent - await page.goto('about:blank'); - await page.close(); - } - // Wait some time - await new Promise(resolve => setTimeout(resolve, 1000)); + // Wait some time + //await new Promise(resolve => setTimeout(resolve, 1000)); + } // Close browser await browser.close(); diff --git a/advanced/multipage-testing-example/client/index.html b/advanced/multipage-testing-example/client/index.html index 6355eef..12c163a 100644 --- a/advanced/multipage-testing-example/client/index.html +++ b/advanced/multipage-testing-example/client/index.html @@ -14,14 +14,14 @@ }(window)) diff --git a/advanced/multipage-testing-example/server/index.js b/advanced/multipage-testing-example/server/index.js index e8165f1..408205e 100644 --- a/advanced/multipage-testing-example/server/index.js +++ b/advanced/multipage-testing-example/server/index.js @@ -3,8 +3,8 @@ const path = require('path'); const express = require('express'); const app = express(); -const Split = require('./split.js'); -const splitClient = Split.getInstance().client; +const getSplitClient = require('./split.js'); +const splitClient = getSplitClient(); // Split traffic to serve two variants of the Web page, using `id` query param as user key for feature flag evaluations // Web page variants are located at different folders: `dist/off` ('off' treatment) and `dist/on` ('on' treatment) @@ -14,8 +14,8 @@ app.use('/off', express.static(path.join(__dirname, '..', 'dist', 'off'))); app.use('/', (req, res, next) => { if (req.query.id) { - const optimizeAsync = splitClient.getTreatment(req.query.id, process.env.FEATURE_FLAG_ASYNC_NAME); - const imageSize = splitClient.getTreatment(req.query.id, process.env.FEATURE_IMAGE_SIZE_NAME); + const optimizeAsync = splitClient.getTreatment(req.query.id, process.env.FEATURE_FLAG_OPTIMIZE_PAGE); + const imageSize = splitClient.getTreatment(req.query.id, process.env.FEATURE_FLAG_IMAGE_SIZE); if (optimizeAsync === 'on') { return res.redirect('/on' + req.url + '&img=' + imageSize) diff --git a/advanced/multipage-testing-example/server/split.js b/advanced/multipage-testing-example/server/split.js index 26999ac..7d5c234 100644 --- a/advanced/multipage-testing-example/server/split.js +++ b/advanced/multipage-testing-example/server/split.js @@ -1,29 +1,29 @@ require('dotenv').config(); const { SplitFactory } = require('@splitsoftware/splitio'); -class PrivateSplit { - constructor() { - this.client = SplitFactory({ - core: { - authorizationKey: process.env.SERVER_SIDE_SDK_KEY, - }, - scheduler: { - impressionsRefreshRate: 2 // s - send information on who got what treatment at - }, // what time back to Split server every 2 seconds - debug: 'INFO' - }).client(); - } -} -class Split { - constructor() { - throw new Error('Use Split.getInstance()'); - } - static getInstance() { - if (!Split.instance) { - Split.instance = new PrivateSplit(); - } - return Split.instance; +// the SDK client singleton instance +let client; + +function getSplitClient() { + + // This implementation of the singleton pattern ensures + // that only one instance of the SplitFactory is created. + // This means that only one copy of the Split (feature flag + // and segment) definitions are downloaded and synchronized. + + if (!client) { + client = SplitFactory({ + core: { + authorizationKey: process.env.SERVER_SIDE_SDK_KEY, + }, + scheduler: { + impressionsRefreshRate: 2 // s - send information on who got what treatment at + }, // what time back to Split server every 2 seconds + debug: 'INFO' + }).client(); } + + return client; } -module.exports = Split; \ No newline at end of file +module.exports = getSplitClient; \ No newline at end of file From 0da050965c40b89ba9e76079516f6a09df9c9b9d Mon Sep 17 00:00:00 2001 From: lenasano Date: Fri, 27 Oct 2023 06:12:18 -0700 Subject: [PATCH 7/7] style improvements and update to readme --- .../multipage-testing-example/.env.example | 2 +- advanced/multipage-testing-example/README.md | 20 ++++++----- .../multipage-testing-example/automation.js | 36 +++++++++++++------ .../client/index-off.js | 5 ++- .../client/index-on.js | 5 ++- 5 files changed, 42 insertions(+), 26 deletions(-) diff --git a/advanced/multipage-testing-example/.env.example b/advanced/multipage-testing-example/.env.example index 3eed8d7..9d63cd8 100644 --- a/advanced/multipage-testing-example/.env.example +++ b/advanced/multipage-testing-example/.env.example @@ -1,5 +1,5 @@ CLIENT_SIDE_SDK_KEY=YOUR_CLIENT_SIDE_SDK_KEY SERVER_SIDE_SDK_KEY=YOUR_SERVER_SIDE_SDK_KEY -FEATURE_FLAG_OPTIMIZE_PAGE=async_optimization +FEATURE_FLAG_OPTIMIZE_PAGE=page_optimization FEATURE_FLAG_NETWORK_SPEED=network_speed FEATURE_FLAG_IMAGE_SIZE=image_size \ No newline at end of file diff --git a/advanced/multipage-testing-example/README.md b/advanced/multipage-testing-example/README.md index 8aeefe6..3ff9b87 100755 --- a/advanced/multipage-testing-example/README.md +++ b/advanced/multipage-testing-example/README.md @@ -1,6 +1,6 @@ -The example consists of an A/B test comparing the performance of two variants of a web page. A NodeJS server serves these variants behind a feature flag using Split's NodeJS SDK. The web pages are built with Webpack and utilize the Split's RUM Agent to capture performance metrics such as page load time and Web-vitals. +The example consists of an A/B test comparing the performance of two variants of a webpage. A NodeJS server serves these variants behind a feature flag using Split's NodeJS SDK. The webpages are built with Webpack and utilize Split RUM Agent to capture performance metrics such as page load time and Web Vitals. -One of the variants (treatment 'on') includes performance optimizations, while the other (treatment 'off') doesn't. For example, the 'on' variant loads the RUM agent using dynamic imports and is built with Webpack's "production" mode, which minifies the code. In contrast, the 'off' variant loads the RUM Agent synchronously, blocking the page load, and is built with Webpack's "development" mode that doesn't minifies the code by default. +One of the webpage variants (treatment 'on') includes performance optimizations, while the other (treatment 'off') doesn't. For example, the 'on' variant loads the RUM agent using dynamic imports and is built with Webpack's "production" mode, which minifies the code. In contrast, the 'off' variant loads the RUM Agent synchronously, blocking the page load, and is built with Webpack's "development" mode that doesn't minify the code by default. An automation script navigates the page multiple times, generating events and impressions for both treatments. @@ -9,19 +9,19 @@ An automation script navigates the page multiple times, generating events and im This example assumes you have set up a feature flag in an environment, with traffic type 'user' and two treatments: 'on' and 'off'. 1. Take a copy of `.env.example` and re-name to `.env`. -2. Add your Split SDK keys and feature flag name to `.env`. +2. Add your Split SDK keys and feature flag names to `.env`. 3. Run `npm install` to install dependencies. 4. Run `npm run serve` to build the app and start the server. The Web page will be served at `http://localhost:3000/?id=`, where `` is a unique identifier for the user, used by the Split SDK to bucket the user into a treatment. -5. Run `npm run automation` to run the automation script. The script can take a few minutes to complete, as it will generate events and impressions by navigating to the Web page multiple times with different user IDs, using [Puppeteer and Chrome Headless](https://www.npmjs.com/package/puppeteer). -6. Open the Split UI, create metrics associated to the events captured by the RUM Agent, and analyze the results. For example, in the Create Metric panel, you can create a metric for page load time, by selecting traffic type `user`, event type `page.load.time`, desired impact "Decrease", and measured as "Average of event values per user". See more about ["Creating a Metric"](https://help.split.io/hc/en-us/articles/360020586132-Creating-a-metric) and ["Metrics impact tab"](https://help.split.io/hc/en-us/articles/360020844451-Metrics-impact-tab). +5. Run `npm run automation` to run the automation script. The script can take some time to complete, as it will generate events and impressions by navigating to the webpage multiple times with different user IDs, using [Puppeteer and Chrome](https://www.npmjs.com/package/puppeteer). You can grab a coffee. :) +6. Open the Split UI, click on a feature flag's Metrics impact tab, and analyze the metric results. You can click 'View more' on a Metric card to visualize metric measurements. See more about the ["Metrics impact tab"](https://help.split.io/hc/en-us/articles/360020844451-Metrics-impact-tab). ![Split UI](./screenshot.png) # Contents -- `/client`: source code of the Web application and its two variants. - - `/client/index-on.js`: entry point for the optimized variant, served for treatment `on`. - - `/client/index-off.js`: entry point for the default variant, served for treatment `off`. +- `/client`: source code of the web application and its two variants. + - `/client/index-on.js`: entry point for the optimized variant, served for treatment `on` of the Split feature flag provided to the `FEATURE_FLAG_OPTIMIZE_PAGE` variable (after creating this flag in the Split UI, you provide the flag name in your `.env` file). + - `/client/index-off.js`: entry point for the default variant, served for treatment `off` of the `FEATURE_FLAG_OPTIMIZE_PAGE` Split feature flag. - `/webpack.config.js`: Webpack configuration to build the two variants of the application. - `/dist`: built static assets of the application, generated by Webpack. - `/server/index.js`: source code of the NodeJS server that implements the endpoint `GET /?id=` that serves the two variants of the application behind a feature flag. @@ -33,3 +33,7 @@ This example assumes you have set up a feature flag in an environment, with traf - `npm run build`: builds the two variants of the application. - `npm run serve`: builds the two variants of the application and starts the NodeJS server. - `npm run automation`: runs the automation script. + +# Tutorial + +This version of the code was used to create the [blog](https://www.split.io/blog/): 'Instant feature impact detection for webpage performance: Split’s hidden gem'. Since you are already peering into the code, check it out and see Split's powerful IFID capabilities! \ No newline at end of file diff --git a/advanced/multipage-testing-example/automation.js b/advanced/multipage-testing-example/automation.js index c4c19cf..2d42d91 100644 --- a/advanced/multipage-testing-example/automation.js +++ b/advanced/multipage-testing-example/automation.js @@ -4,7 +4,16 @@ const puppeteer = require('puppeteer'); const getSplitClient = require('./server/split.js'); const splitClient = getSplitClient(); -const SAMPLE_SIZE = 100; + +// You can optionally pass the loop variables on the command line, e.g. `npm run automation 0 200` +let [i, SAMPLE_SIZE] = process.argv.slice(2,4).map(n => parseInt(n, 10)); +// Validate input +if ( !( i <= SAMPLE_SIZE ) ) { + console.log(`Found invalid command line arguments '${i}' and '${SAMPLE_SIZE}'. Using default values instead.`); + i = 0; + SAMPLE_SIZE = 200; +} + // https://fdalvi.github.io/blog/2018-02-05-puppeteer-network-throttle/ const NETWORK_CONDITIONS = { @@ -35,11 +44,11 @@ const NETWORK_CONDITIONS = { (async () => { console.log('Navigation script'); - // Launch browser + // Launch browser, use headful mode to allow Cumulative Layout Shift (CLS) measurment const browser = await puppeteer.launch({headless: false, defaultViewport: null}); - for (let id = 0; id < SAMPLE_SIZE; id++) { - console.log(`Running ${id} of ${SAMPLE_SIZE}`); + for (; i < SAMPLE_SIZE; i++) { + console.log(`Running ${i} of ${SAMPLE_SIZE}`); // Open new page const page = await browser.newPage(); @@ -48,7 +57,7 @@ const NETWORK_CONDITIONS = { await page.setCacheEnabled(false); // Evaluate Split flag to determine what the emulated network conditions should be for this user - const networkSpeed = splitClient.getTreatment(id, process.env.FEATURE_FLAG_NETWORK_SPEED); + const networkSpeed = splitClient.getTreatment(i, process.env.FEATURE_FLAG_NETWORK_SPEED); await page.emulateNetworkConditions( (networkSpeed in NETWORK_CONDITIONS) @@ -57,16 +66,21 @@ const NETWORK_CONDITIONS = { ); // Navigate to URL - await page.goto(`http://localhost:3000/?id=${id}`, { waitUntil: "networkidle0" }); + await page.goto (`http://localhost:3000/?id=${i}` , {waitUntil: "networkidle0", timeout: 0}); // Disabled timeout to avoid exception being thrown. If, however, the page gets 'stuck', click the refresh button. - // Close the tab so that the Web Vitals Interaction to Next Paint (INP) - // and Cumulative Layout Shift (CLS) measurements will be sent - await page.close(); + // Click on an element to start measuring First Input Delay (FID) and Interaction to Next Paint (INP) time + await page.click('#split_logo'); - // Wait some time - //await new Promise(resolve => setTimeout(resolve, 1000)); + // Pause to allow time for the FID and INP measurement + await new Promise(resolve => setTimeout(resolve, 1000)); + + // Close the tab so that the CLS and INP measurements are sent + await page.close(); } + // Pause to allow the browser time to send the last CLS measurement + await new Promise(resolve => setTimeout(resolve, 1000)); + // Close browser await browser.close(); diff --git a/advanced/multipage-testing-example/client/index-off.js b/advanced/multipage-testing-example/client/index-off.js index e8869ad..00b9857 100755 --- a/advanced/multipage-testing-example/client/index-off.js +++ b/advanced/multipage-testing-example/client/index-off.js @@ -3,9 +3,8 @@ import { SplitRumAgent, webVitals } from '@splitsoftware/browser-rum-agent'; SplitRumAgent .setup(process.env.CLIENT_SIDE_SDK_KEY, - { - pushRate: 2 // seconds - post queued event data to Split backend every 2 seconds - } + // set 2 second pushRate so we can view events in realtime in Split Data hub + { pushRate: 2 } ) .addIdentities([ // get key from URL query parameter `id` diff --git a/advanced/multipage-testing-example/client/index-on.js b/advanced/multipage-testing-example/client/index-on.js index ba838ae..313051a 100755 --- a/advanced/multipage-testing-example/client/index-on.js +++ b/advanced/multipage-testing-example/client/index-on.js @@ -3,9 +3,8 @@ import('./browser-rum-agent').then(({ SplitRumAgent, webVitals }) => { SplitRumAgent .setup(process.env.CLIENT_SIDE_SDK_KEY, - { - pushRate: 2 // seconds - post queued event data to Split backend every 2 seconds - } + // set 2 second pushRate so we can view events in realtime in Split Data hub + { pushRate: 2 } ) .addIdentities([ // get key from URL query parameter `id`