diff --git a/.github/workflows/linux-chrome.yml b/.github/workflows/linux-chrome.yml index 95ed4092c..3f174b205 100644 --- a/.github/workflows/linux-chrome.yml +++ b/.github/workflows/linux-chrome.yml @@ -198,6 +198,51 @@ jobs: jq -e '.[0].browserScripts[0].timings.softNavigations[0].name | endswith("/spa/page-3")' /tmp/soft-nav-no-request/browsertime.json jq -e '.log.pages[0]._softNavigation == true' /tmp/soft-nav-no-request/browsertime.har jq -e '.log.pages[0]._url | endswith("/spa/page-3")' /tmp/soft-nav-no-request/browsertime.har + - name: Test Chrome SPA user journey over multiple routes + run: | + ./bin/browsertime.js -b chrome -n 1 --xvfb --spa --resultDir /tmp/spa-journey test/data/commandscripts/spaJourney.cjs + echo "--- measured pages ---" + jq '[.[] | {url: .info.url, alias: .info.alias}]' /tmp/spa-journey/browsertime.json + echo "--- HAR pages ---" + jq '.log.pages | map({id, _url, _softNavigation})' /tmp/spa-journey/browsertime.har + jq -e '. | length == 3' /tmp/spa-journey/browsertime.json + jq -e '.[0].browserScripts[0].timings.softNavigations[0].name | endswith("/spa/products")' /tmp/spa-journey/browsertime.json + jq -e '.[1].browserScripts[0].timings.softNavigations[0].name | endswith("/spa/about")' /tmp/spa-journey/browsertime.json + jq -e '.[2].browserScripts[0].timings.softNavigations[0].name | endswith("/spa/gallery")' /tmp/spa-journey/browsertime.json + jq -e '.log.pages | length == 3' /tmp/spa-journey/browsertime.har + jq -e '[.log.pages[] | select(._softNavigation == true)] | length == 3' /tmp/spa-journey/browsertime.har + jq -e '(.log.pages[0].id) as $p | [.log.entries[] | select(.pageref == $p and (.request.url | contains("products.json")))] | length == 1' /tmp/spa-journey/browsertime.har + jq -e '(.log.pages[2].id) as $p | [.log.entries[] | select(.pageref == $p and (.request.url | contains("pirate.png")))] | length == 1' /tmp/spa-journey/browsertime.har + - name: Test Chrome SPA user journey mixing soft and hard navigations + run: | + ./bin/browsertime.js -b chrome -n 1 --xvfb --spa --resultDir /tmp/spa-mixed test/data/commandscripts/spaMixedJourney.cjs + echo "--- measured pages ---" + jq '[.[] | {url: .info.url, alias: .info.alias}]' /tmp/spa-mixed/browsertime.json + echo "--- HAR pages ---" + jq '.log.pages | map({id, _url, _softNavigation})' /tmp/spa-mixed/browsertime.har + jq -e '. | length == 3' /tmp/spa-mixed/browsertime.json + jq -e '.[0].browserScripts[0].timings.softNavigations[0].name | endswith("/spa/about")' /tmp/spa-mixed/browsertime.json + jq -e '.[1].info.url | endswith("/simple/")' /tmp/spa-mixed/browsertime.json + jq -e '.[1].browserScripts[0].timings.softNavigations == null' /tmp/spa-mixed/browsertime.json + jq -e '.[2].browserScripts[0].timings.softNavigations[0].name | endswith("/spa/products")' /tmp/spa-mixed/browsertime.json + jq -e '.log.pages | length == 3' /tmp/spa-mixed/browsertime.har + jq -e '.log.pages[0]._softNavigation == true' /tmp/spa-mixed/browsertime.har + jq -e '.log.pages[1]._softNavigation == null' /tmp/spa-mixed/browsertime.har + jq -e '(.log.pages[1].id) as $p | [.log.entries[] | select(.pageref == $p)] | length > 0' /tmp/spa-mixed/browsertime.har + - name: Test Chrome SPA user journey without any requests + run: | + ./bin/browsertime.js -b chrome -n 1 --xvfb --spa --resultDir /tmp/spa-no-request test/data/commandscripts/spaNoRequestJourney.cjs + echo "--- measured pages ---" + jq '[.[] | {url: .info.url, alias: .info.alias}]' /tmp/spa-no-request/browsertime.json + echo "--- HAR pages ---" + jq '.log.pages | map({id, _url, _softNavigation})' /tmp/spa-no-request/browsertime.har + jq -e '. | length == 2' /tmp/spa-no-request/browsertime.json + jq -e '.[0].browserScripts[0].timings.softNavigations | length == 1' /tmp/spa-no-request/browsertime.json + jq -e '.[1].browserScripts[0].timings.softNavigations | length == 1' /tmp/spa-no-request/browsertime.json + jq -e '.[0].browserScripts[0].timings.softNavigations[0].name | endswith("/spa/about")' /tmp/spa-no-request/browsertime.json + jq -e '.[1].browserScripts[0].timings.softNavigations[0].name | endswith("/spa/")' /tmp/spa-no-request/browsertime.json + jq -e '[.log.pages[] | select(._softNavigation == true)] | length == 2' /tmp/spa-no-request/browsertime.har + jq -e '.log.entries | length == 0' /tmp/spa-no-request/browsertime.har video: runs-on: ubuntu-22.04 steps: diff --git a/.github/workflows/linux-firefox.yml b/.github/workflows/linux-firefox.yml index 1e0dd7502..00faa5398 100644 --- a/.github/workflows/linux-firefox.yml +++ b/.github/workflows/linux-firefox.yml @@ -79,3 +79,14 @@ jobs: run: ./bin/browsertime.js -b firefox http://127.0.0.1:3000/simple/ -n 1 --viewPort 1000x600 --xvfb --enableProfileRun - name: Test Firefox BiDi and profiler commands run: ./bin/browsertime.js -b firefox test/data/commandscripts/firefox.cjs -n 1 --xvfb + - name: Test Firefox SPA user journey over multiple routes + run: | + ./bin/browsertime.js -b firefox -n 1 --xvfb --spa --resultDir /tmp/spa-journey test/data/commandscripts/spaJourney.cjs + echo "--- measured pages ---" + jq '[.[] | {url: .info.url, alias: .info.alias}]' /tmp/spa-journey/browsertime.json + jq -e '. | length == 3' /tmp/spa-journey/browsertime.json + jq -e '.[0].info.url | endswith("/spa/products")' /tmp/spa-journey/browsertime.json + jq -e '.[1].info.url | endswith("/spa/about")' /tmp/spa-journey/browsertime.json + jq -e '.[2].info.url | endswith("/spa/gallery")' /tmp/spa-journey/browsertime.json + # Firefox has no soft navigation API, so no per-route entries are expected + jq -e '.[0].browserScripts[0].timings.softNavigations == null' /tmp/spa-journey/browsertime.json diff --git a/test/data/commandscripts/spaJourney.cjs b/test/data/commandscripts/spaJourney.cjs new file mode 100644 index 000000000..9436a1d1d --- /dev/null +++ b/test/data/commandscripts/spaJourney.cjs @@ -0,0 +1,18 @@ +module.exports = async function (context, commands) { + await commands.navigate('http://127.0.0.1:3000/spa/'); + // A route that fetches data before it can render + await commands.measure.start('products'); + await commands.mouse.singleClick.bySelector('#nav-products'); + await commands.wait.byPageToComplete(); + await commands.measure.stop(); + // A route rendered entirely from memory, no requests + await commands.measure.start('about'); + await commands.mouse.singleClick.bySelector('#nav-about'); + await commands.wait.byPageToComplete(); + await commands.measure.stop(); + // A route that loads an image + await commands.measure.start('gallery'); + await commands.mouse.singleClick.bySelector('#nav-gallery'); + await commands.wait.byPageToComplete(); + return commands.measure.stop(); +}; diff --git a/test/data/commandscripts/spaMixedJourney.cjs b/test/data/commandscripts/spaMixedJourney.cjs new file mode 100644 index 000000000..9b3753ac8 --- /dev/null +++ b/test/data/commandscripts/spaMixedJourney.cjs @@ -0,0 +1,16 @@ +module.exports = async function (context, commands) { + await commands.navigate('http://127.0.0.1:3000/spa/'); + // A soft navigation without any request + await commands.measure.start('spa-about'); + await commands.mouse.singleClick.bySelector('#nav-about'); + await commands.wait.byPageToComplete(); + await commands.measure.stop(); + // A classic hard navigation in the middle of the journey + await commands.measure.start('http://127.0.0.1:3000/simple/'); + // Back into the SPA for a soft navigation with a request + await commands.navigate('http://127.0.0.1:3000/spa/'); + await commands.measure.start('spa-products'); + await commands.mouse.singleClick.bySelector('#nav-products'); + await commands.wait.byPageToComplete(); + return commands.measure.stop(); +}; diff --git a/test/data/commandscripts/spaNoRequestJourney.cjs b/test/data/commandscripts/spaNoRequestJourney.cjs new file mode 100644 index 000000000..3eb5b295f --- /dev/null +++ b/test/data/commandscripts/spaNoRequestJourney.cjs @@ -0,0 +1,12 @@ +module.exports = async function (context, commands) { + await commands.navigate('http://127.0.0.1:3000/spa/'); + // Two consecutive soft navigations that never touch the network + await commands.measure.start('about'); + await commands.mouse.singleClick.bySelector('#nav-about'); + await commands.wait.byPageToComplete(); + await commands.measure.stop(); + await commands.measure.start('home'); + await commands.mouse.singleClick.bySelector('#nav-home'); + await commands.wait.byPageToComplete(); + return commands.measure.stop(); +}; diff --git a/test/data/html/spa/index.html b/test/data/html/spa/index.html index 08ffa7153..89c746d16 100644 --- a/test/data/html/spa/index.html +++ b/test/data/html/spa/index.html @@ -3,15 +3,95 @@
This page changes its URL via history.pushState when you click the - button. Combined with the user click and the new content painted into - the page, Chrome treats the transition as a soft navigation.
+ +This page changes its URL via history.pushState when you click a + link or button. Combined with the user click and the new content painted + into the page, Chrome treats the transition as a soft navigation.
+