Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions .github/workflows/linux-chrome.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
11 changes: 11 additions & 0 deletions .github/workflows/linux-firefox.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
18 changes: 18 additions & 0 deletions test/data/commandscripts/spaJourney.cjs
Original file line number Diff line number Diff line change
@@ -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();
};
16 changes: 16 additions & 0 deletions test/data/commandscripts/spaMixedJourney.cjs
Original file line number Diff line number Diff line change
@@ -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();
};
12 changes: 12 additions & 0 deletions test/data/commandscripts/spaNoRequestJourney.cjs
Original file line number Diff line number Diff line change
@@ -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();
};
86 changes: 83 additions & 3 deletions test/data/html/spa/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,95 @@
<head>
<meta charset="utf-8" />
<title>SPA home</title>
<style>
nav a {
margin-right: 16px;
font-size: 20px;
}
#view {
font-size: 32px;
padding: 40px;
}
</style>
</head>
<body>
<h1 id="title">Home</h1>
<p>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.</p>
<nav>
<a id="nav-home" href="/spa/">Home</a>
<a id="nav-products" href="/spa/products">Products</a>
<a id="nav-about" href="/spa/about">About</a>
<a id="nav-gallery" href="/spa/gallery">Gallery</a>
</nav>
<div id="view">
<p>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.</p>
</div>
<button id="goto-page-2">Go to page 2</button>
<button id="goto-page-3">Go to page 3 (no request)</button>
<script>
var view = document.getElementById('view');
var title = document.getElementById('title');

var routes = {
'/spa/': function () {
title.textContent = 'Home';
view.textContent =
'Welcome home, rendered from memory without any network request.';
},
// A route that needs a network request before it can render.
'/spa/products': function () {
title.textContent = 'Products';
fetch('/spa/products.json?ts=' + Date.now())
.then(function (response) {
return response.json();
})
.then(function (products) {
var list = document.createElement('ul');
for (var i = 0; i < products.length; i++) {
var item = document.createElement('li');
item.textContent =
products[i].name + ' costs ' + products[i].price;
list.appendChild(item);
}
view.textContent = '';
view.appendChild(list);
});
},
'/spa/about': function () {
title.textContent = 'About';
view.textContent =
'About us, rendered from memory without any network request.';
},
// A route that loads an image, so the request shows up as a
// resource entry instead of a fetch.
'/spa/gallery': function () {
title.textContent = 'Gallery';
var image = new Image();
image.src = '/img/pirate.png?ts=' + Date.now();
image.width = 400;
view.textContent = '';
view.appendChild(image);
}
};

function render(path) {
(routes[path] || routes['/spa/'])();
}

document.querySelector('nav').addEventListener('click', function (event) {
var path = event.target.getAttribute('href');
if (!routes[path]) return;
event.preventDefault();
history.pushState({}, '', path);
render(path);
});

// Re-render when the user goes back or forward in history.
window.addEventListener('popstate', function () {
render(location.pathname);
});

document.getElementById('goto-page-2').addEventListener('click', function () {
history.pushState({}, '', '/spa/page-2');
// Trigger a real network request so chrome-har keeps the page
Expand Down
5 changes: 5 additions & 0 deletions test/data/html/spa/products.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[
{ "name": "Pirate hat", "price": 120 },
{ "name": "Wooden leg", "price": 340 },
{ "name": "Parrot", "price": 560 }
]
Loading