dsw
Version:
Dynamic Service Worker, offline Progressive Web Apps much easier
723 lines (663 loc) • 25.1 kB
HTML
<html lang="en">
<head>
<meta charset="UTF-8">
<title>DSW Tests</title>
<link rel="icon" type="image/png" href="http://localhost:8888/helmet.png">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<style>
html, body {
margin: 0;
height: 100%;
background: #ffd;
font-family: Helvetica, Arial, sans-serif;
}
*, *:before, *:after {
box-sizing: border-box;
}
#status {
float: left;
width: 25%;
border-left: solid 1px black;
margin: 0;
padding-left: 5px;
}
#status li.title {
font-size: 2em;
font-weight: bold;
padding-top: 22px;
padding-bottom: 22px;
}
#status li.title:before {
content: "";
}
#status li {
padding-left: 20px;
}
#status li:before {
content: "◷";
position: absolute;
left: 10px;
}
#status li:not(.passed):not(.failed):not(.title):before {
font-size: 1.3em;
margin-top: -5px;
animation-name: spin;
animation-duration: 1s;
animation-iteration-count: infinite;
animation-timing-function: linear;
transform-origin: 50% 50%;
width: 12px;
height: 27px;
display: inline-block;
}
#status li.passed:before {
content: "✔ ";
color: green;
}
#status li.failed:before {
content: "✖ ";
color: red;
}
#testPage {
float: right;
width: 75%;
height: 100%;
border: none;
border-left: solid 1px black;
background: -moz-linear-gradient(-45deg, #f0f0f0 0%, #d2d3d5 40%, #eff0f2 100%);
background: -webkit-linear-gradient(-45deg, #f0f0f0 0%,#d2d3d5 40%,#eff0f2 100%);
background: linear-gradient(135deg, #f0f0f0 0%,#d2d3d5 40%,#eff0f2 100%);
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#f0f0f0', endColorstr='#eff0f2',GradientType=1 );
font-family: Helvetica, Arial, sans-serif;
}
@keyframes spin {
from { transform: rotate(0) }
to { transform: rotate(360deg) }
}
</style>
<ol id="status">
<li class="title">Tests</li>
</ol>
<iframe src="" frameborder="0" id="testPage"></iframe>
<script>
var steps = {
ok: 0,
fail: 0,
list: []
};
var step = null;
var fullTestsListResolver;
var unregistered = false;
const fullTestsList = new Promise(function(resolve, reject){
fullTestsListResolver = resolve;
});
function addStep (step, status) {
var item = document.createElement('li');
item.innerHTML = step;
//item.classList.add(!status || status == 'ok' || status === true? 'ok': 'fail');
document.getElementById('status').appendChild(item);
item.ok = function () {
this.classList.add('passed');
steps.ok++;
};
item.fail = function () {
this.classList.add('failed');
steps.fail++;
};
if (status === false) {
item.fail();
}
if (status === true) {
item.ok();
}
return item;
}
var traced = {};
var tracing = {};
var iframe = document.getElementById('testPage');
const SANDBOX_URL = 'http://localhost:8888/';
function traceReceived (event) {
traced[event.data.trace.src] = event.data.trace;
if (tracing[event.data.trace.src]) {
tracing[event.data.trace.src](event.data.trace);
}
}
function sendMessage (message) {
return new Promise((resolve, reject)=>{
var messageChannel = new MessageChannel();
messageChannel.port1.onmessage = function messageReceived (event) {
if (!event.data) {
reject('No data arrived');
return;
}
if (event.data && event.data.acknowledged !== void(0)) {
resolve(event.data.acknowledged || false);
return;
} else {
if (event.data.trace) {
traceReceived(event);
}
}
resolve(event.data);
};
document.getElementById('testPage')
.contentWindow
.postMessage(message, '*', [messageChannel.port2]);
});
}
function waitForSandboxToLoad () {
return new Promise((resolve, reject)=>{
step = addStep('Load sandbox');
var wasOk = false;
iframe.onload = function () {
resolve();
step.ok();
wasOk = true;
}
iframe.setAttribute('src', SANDBOX_URL);
setTimeout(_=>{
if (!wasOk) {
step.fail();
}
}, 6000);
});
}
function waitForDSWRegistration () {
step = addStep('Register SW');
var wasOk = false;
return new Promise((resolve, reject)=>{
setTimeout(_=>{
if (!wasOk) {
step.fail();
reject();
}
}, 5000);
sendMessage({
DSWCommand: {
get: 'readyEvent'
}
}).then(result=>{
if (result.status.registered) {
wasOk = true;
step.ok();
resolve(result);
} else {
step.fail();
reject();
}
addStep('Pre cache AppShell', result.status.appShell);
}).catch(err=>{
reject(err);
});
});
}
function waitForSandboxToReload () {
return new Promise((resolve, reject)=>{
// we have to do it this way due to an error freezing the devTools
// in chrome when we try to access the contentWindow in an iframe that
// we have changed the source.
step = addStep('Reload Sandbox');
setTimeout(_=>{
var finalIframe = document.createElement('iframe');
var wasOk = false;
finalIframe.setAttribute('id', 'testPage');
finalIframe.onload = function(){
resolve();
step.ok();
wasOk = true;
};
setTimeout(_=>{
if (!wasOk) {
step.fail();
reject();
}
}, 5000);
var src = iframe.src;
finalIframe.src = src;
iframe.replaceWith(finalIframe);
iframe = finalIframe;
}, 1000);
});
}
function turnTestsOn () {
let step = addStep ('Enable test mode');
return sendMessage({
DSWCommand: {
dswUnderAutomatedTest: true
}
}).then(result=>{
if (result === true) {
step.ok();
} else {
debugger;
step.fail();
}
});
}
function getDSWStatus () {
step = addStep('Get DSW Status');
return sendMessage({
DSWCommand: {
get: 'dswStatus'
}
}).then(result=>{
if (result && result.registered) {
return step.ok();
}
step.fail();
});
}
var testsToRun = {};
var currentTestGroup = 0;
function addTest (text, test, testGroup) {
var testStart = performance.now();
testGroup = testGroup || 0;
testsToRun[testGroup] = testsToRun[testGroup] || [];
testsToRun[testGroup].push(function () {
return new Promise((resolve, reject)=>{
var currentTest = addStep(' - ' + text);
var timeout = setTimeout(_=>{
reject('"'+ text + '" timed out!');
}, 5000);
test().then(result=>{
currentTest[result? 'ok': 'fail'](result);
clearTimeout(timeout);
resolve(result);
steps.list.push({
"name": text,
"result": true,
"message": "success",
"duration": performance.now() - testStart
});
}).catch(err=>{
reject('"'+ text + '" test failed executing');
console.error(err);
steps.list.push({
"name": text,
"result": false,
"message": err,
"duration": performance.now() - testStart
});
});
})
});
}
function nextTestGroup () {
return new Promise((resolve, reject)=>{
setTimeout(_=>{
turnTestsOn()
.then(_=>{
traced = {};
currentTestGroup++;
resolve();
})
.catch(err=>{
reject();
});
}, 1000);
});
}
function execTests () {
step = addStep('Requests Group '+currentTestGroup);
function* testGen () {
while(true){
var test = testsToRun[currentTestGroup].shift();
if (test) {
yield test();
} else {
return;
}
}
}
var tests = testGen();
// let's run all the promises
return Promise.all([...tests])
.then(result=>{
step.ok();
})
.catch(err=>{
step.fail(err);
});
}
function unregisterAndCleanUp () {
step = addStep('Unregistering');
return sendMessage({
DSWCommand: {
exec: 'unregister'
}
}).then(output=>{
if (output.result.unregistered) {
unregistered = true;
step.ok();
} else {
step.fail();
}
});
}
function closeTests () {
var total = (steps.ok + steps.fail);
var text = '<strong>Finished:</strong>' +
'<br/>' + total + ' tests'+
'<br/>Tests passed: ' + steps.ok +
'<br/>Tests failed: ' + steps.fail +
'<br/>';
addStep(text, steps.fail? false: true);
fullTestsListResolver(!steps.fail);
// exposing it to sauce_labs
window.global_test_results = {
"passed": steps.ok,
"failed": steps.fail,
"total": total,
"duration": (performance.now() - startingTime).toFixed(0),
"tests": steps.list
}
}
function validate (expected, result) {
return new Promise((resolve, reject)=>{
if (expected.debug) {
debugger;
}
function fail (message) {
console.error('[ TEST_FAIL ] :: ' + (message || ''), expected, '\n', result);
resolve(false);
}
if (expected.length && result.steps.length != expected.length) {
return fail('Number of steps', expected, result);
}
if (expected.redirectedTo && result.redirectedTo != expected.redirectedTo) {
return fail('Was supposed to redirect to ' + expected.redirectedTo);
}
if (expected.follows) {
// it should follow the steps in order
// (extra steps in between are allowed, though)
let nextStep = new RegExp(expected.follows.shift(), 'i');
result.steps.forEach(cur=>{
if (cur.step.match(nextStep)) {
nextStep = new RegExp(expected.follows.shift(), 'i');
}
});
if (expected.follows.length) {
return fail('Did not follow all the steps in order!\nMissed steps:\n - ' + expected.follows.join('\n - ') + '\n\n');
}
}
if (expected.steps) {
let i = 0,
curStep = {};
try {
for(i in expected.steps) {
curStep = expected.steps[i];
if (i == 'last') {
i = result.steps.length - 1;
}
// testing for the resulting url
let stepData = result.steps[i].data;
if (curStep.url) {
// we check the url in the given step
if (stepData.url != curStep.url) {
return fail('Url mismatch in step ' + i);
}
}
if (curStep.rule) {
// we check the rule name in the given step
if ((stepData.name || stepData.rule.name) != curStep.rule) {
return fail('Rule name mismatch in step ' + i);
}
}
if (curStep.strategy) {
if (stepData.rule.strategy != curStep.strategy) {
return fail('Strategy mismatch for step ' + i);
}
}
if (curStep.cache) {
if (stepData.rule.action.cache) {
return fail('Step ' + i + ' should be caching');
}
}
if (curStep.indexeddb) {
if (stepData.rule.action.indexeddb) {
return fail('Step ' + i + ' should have indexedDB action ');
}
}
}
}catch(e){
return fail(e.message + '\nIn step ' + i);
}
}
resolve({ status: true });
});
}
function assert (output, expected) {
return new Promise((resolve, reject)=>{
var data = traced[output.result.url];
if (data) {
resolve(validate(expected, data));
} else {
tracing[output.result.url] = traceData=>{
resolve(validate(expected, traceData));
};
}
});
}
addTest('Load image', function () {
return new Promise((resolve, reject)=>{
sendMessage({
DSWCommand: {
exec: 'click',
target: '#btn-img-1'
}
}).then(result=>{
resolve(assert(result, {
length: 7,
follows: [
'Best matching rule found: "images"',
/Received result OK \(200\)/,
"Saving into cache",
"Responded"
],
steps: {
0: { url: 'http://localhost:8888/images/public/gears.png' },
1: { rule: 'images', strategy: 'offline-first' },
6: { url: 'http://localhost:8888/images/public/gears.png' }
}
}));
})
});
});
addTest('Image not found', function () {
return new Promise((resolve, reject)=>{
sendMessage({
DSWCommand: {
exec: 'click',
target: '#btn-img-2'
}
}).then(result=>{
resolve(assert(result, {
follows: [
'Will fetch',
'Failed fetching',
'fallback',
'Redirecting',
'Result found in cache'
],
steps: {
1: { rule: 'images'},
6: { rule: 'imageNotFound' },
8: { rule: 'images' },
'last': { url: 'http://localhost:8888/images/public/404.jpg' }
}
}));
})
});
});
addTest('Redirect image', function () {
return new Promise((resolve, reject)=>{
sendMessage({
DSWCommand: {
exec: 'click',
target: '#btn-img-3'
}
}).then(result=>{
resolve(assert(result, {
redirectedTo: 'http://localhost:8888/images/public/gizmo.jpg',
follows: [
'redirectOlderPage',
'Redirecting',
'Best matching rule found: "images"',
'found in cache'
],
steps: {
'last': { url: 'http://localhost:8888/images/public/gizmo.jpg' }
}
}));
})
});
});
addTest('Uncacheable image', function () {
return new Promise((resolve, reject)=>{
sendMessage({
DSWCommand: {
exec: 'click',
target: '#btn-img-4'
}
}).then(result=>{
resolve(assert(result, {
follows: [
'imageNotCached',
'online first strategy',
'Responded'
]
}));
})
});
});
addTest('Page not found', function () {
return new Promise((resolve, reject)=>{
sendMessage({
DSWCommand: {
exec: 'click',
target: '#btn-5-page'
}
}).then(result=>{
resolve(assert(result, {
//debug: true,
redirectedTo: 'http://localhost:8888/not-found.html?from=/foo.html',
follows: [
'static-html',
'using fastest strategy',
'Fastest strategy failed fetching',
'Fastest strategy could not fetch nor find in cache',
'Found fallback rule',
'Must redirect',
'static-html',
'using fastest strategy',
'Result found in cache'
]
}));
})
});
});
addTest('Load JSON', function () {
return new Promise((resolve, reject)=>{
sendMessage({
DSWCommand: {
exec: 'click',
target: '#btn-6-data'
}
}).then(result=>{
resolve(assert(result, {
follows: [
'Best matching rule found: "userData"',
'Info: Using offline first strategy',
'Will fetch',
'Response object saved into IndexedDB'
]
}));
})
});
});
addTest('Load image again', function () {
return new Promise((resolve, reject)=>{
sendMessage({
DSWCommand: {
exec: 'click',
target: '#btn-img-1'
}
}).then(result=>{
resolve(assert(result, {
length: 5,
follows: [
'Best matching rule found: "images"',
'Info: Using offline first strategy',
'Result found in cache'
]
}));
})
});
}, 1);
addTest('Uncacheable image again', function () {
return new Promise((resolve, reject)=>{
sendMessage({
DSWCommand: {
exec: 'click',
target: '#btn-img-4'
}
}).then(result=>{
resolve(assert(result, {
follows: [
'imageNotCached',
'online first strategy',
'Responded'
]
}));
})
});
}, 1);
addTest('Load JSON again', function () {
return new Promise((resolve, reject)=>{
sendMessage({
DSWCommand: {
exec: 'click',
target: '#btn-6-data'
}
}).then(result=>{
resolve(assert(result, {
follows: [
'Best matching rule found: "userData"',
'Info: Using offline first strategy',
'Found stored in IndexedDB'
]
}));
})
});
}, 1);
// TODO: get the currently used storage
var startingTime = performance.now();
waitForSandboxToLoad()
//.then(unregisterAndCleanUp)
.then(waitForDSWRegistration)
.then(waitForSandboxToReload)
.then(turnTestsOn)
.then(getDSWStatus)
.then(execTests)
.then(waitForSandboxToReload)
.then(nextTestGroup)
.then(execTests)
.then(unregisterAndCleanUp)
.then(result=>{
closeTests();
})
.catch(error=>{
if (step) {
step.fail();
}
if (!unregistered) {
unregisterAndCleanUp();
}
closeTests();
});
</script>
</body>
</html>