Inspired by [Stefan Burnickis](https://github.com/sburnicki) work on https://github.com/iteratec/wpt-filmstrip
This commit is contained in:
parent
f5ce6c7851
commit
38b743e221
|
|
@ -267,6 +267,34 @@ module.exports.parseCommandLine = function parseCommandLine() {
|
|||
describe: 'Add timer and metrics to the video',
|
||||
group: 'Browser'
|
||||
})
|
||||
.option('browsertime.videoParams.filmstripFullSize', {
|
||||
alias: 'videoParams.filmstripFullSize',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
describe:
|
||||
'Keep original sized screenshots in the filmstrip. Will make the run take longer time',
|
||||
group: 'Filmstrip'
|
||||
})
|
||||
.option('browsertime.videoParams.filmstripQuality', {
|
||||
alias: 'videoParams.filmstripQuality',
|
||||
default: 75,
|
||||
describe: 'The quality of the filmstrip screenshots. 0-100.',
|
||||
group: 'Filmstrip'
|
||||
})
|
||||
.option('browsertime.videoParams.createFilmstrip', {
|
||||
alias: 'videoParams.createFilmstrip',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
describe: 'Create filmstrip screenshots.',
|
||||
group: 'Filmstrip'
|
||||
})
|
||||
.option('filmstrip.showAll', {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
describe:
|
||||
'Show all screenshots in the filmstrip, independent if they have changed or not.',
|
||||
group: 'Filmstrip'
|
||||
})
|
||||
.option('browsertime.userTimingWhitelist', {
|
||||
alias: 'userTimingWhitelist',
|
||||
describe:
|
||||
|
|
|
|||
|
|
@ -41,6 +41,9 @@ module.exports = function storageManager(baseDir, storagePathPrefix, useHash) {
|
|||
getBaseDir() {
|
||||
return baseDir;
|
||||
},
|
||||
getFullPathToURLDir(url) {
|
||||
return path.join(baseDir, pathToFolder(url, useHash));
|
||||
},
|
||||
getStoragePrefix() {
|
||||
return storagePathPrefix;
|
||||
},
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,158 @@
|
|||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
function findFrame(videoFrames, time) {
|
||||
let frame = videoFrames[0];
|
||||
for (let currentFrame of videoFrames) {
|
||||
if (time >= currentFrame.time) {
|
||||
frame = currentFrame;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return frame;
|
||||
}
|
||||
|
||||
function getMetricsFromBrowsertime(data) {
|
||||
const metrics = [];
|
||||
|
||||
if (data.timings.userTimings && data.timings.userTimings.marks) {
|
||||
// Some web sites over loads user timings
|
||||
// So use only the first ones
|
||||
const maxUserTimings = 10;
|
||||
let userTimings = 1;
|
||||
for (let mark of data.timings.userTimings.marks) {
|
||||
metrics.push({
|
||||
metric: mark.name,
|
||||
value: mark.startTime.toFixed()
|
||||
});
|
||||
userTimings++;
|
||||
if (userTimings > maxUserTimings) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
metrics.push({
|
||||
metric: 'fullyLoaded',
|
||||
name: 'Fully Loaded',
|
||||
value: data.fullyLoaded
|
||||
});
|
||||
|
||||
if (data.timings.pageTimings) {
|
||||
metrics.push({
|
||||
metric: 'domContentLoadedTime',
|
||||
name: 'DOM Content Loaded Time',
|
||||
value: data.timings.pageTimings.domContentLoadedTime
|
||||
});
|
||||
metrics.push({
|
||||
metric: 'pageLoadTime',
|
||||
name: 'Page Load Time',
|
||||
value: data.timings.pageTimings.pageLoadTime
|
||||
});
|
||||
}
|
||||
|
||||
if (data.visualMetrics) {
|
||||
metrics.push({
|
||||
metric: 'FirstVisualChange',
|
||||
name: 'First Visual Change',
|
||||
value: data.visualMetrics.FirstVisualChange
|
||||
});
|
||||
metrics.push({
|
||||
metric: 'LastVisualChange',
|
||||
name: 'Last Visual Change',
|
||||
value: data.visualMetrics.LastVisualChange
|
||||
});
|
||||
metrics.push({
|
||||
metric: 'VisualComplete85',
|
||||
name: 'Visual Complete 85%',
|
||||
value: data.visualMetrics.VisualComplete85
|
||||
});
|
||||
metrics.push({
|
||||
metric: 'VisualComplete95',
|
||||
name: 'Visual Complete 95%',
|
||||
value: data.visualMetrics.VisualComplete95
|
||||
});
|
||||
metrics.push({
|
||||
metric: 'VisualComplete99',
|
||||
name: 'Visual Complete 99%',
|
||||
value: data.visualMetrics.VisualComplete99
|
||||
});
|
||||
if (data.visualMetrics.LargestImage) {
|
||||
metrics.push({
|
||||
metric: 'LargestImage',
|
||||
name: 'Largest Image',
|
||||
value: data.visualMetrics.LargestImage
|
||||
});
|
||||
}
|
||||
if (data.visualMetrics.Logo) {
|
||||
metrics.push({
|
||||
metric: 'Logo',
|
||||
name: 'Logo',
|
||||
value: data.visualMetrics.Logo
|
||||
});
|
||||
}
|
||||
if (data.visualMetrics.Heading) {
|
||||
metrics.push({
|
||||
metric: 'Heading',
|
||||
name: 'Heading',
|
||||
value: data.visualMetrics.Heading
|
||||
});
|
||||
}
|
||||
}
|
||||
return metrics.sort((a, b) => a.value - b.value);
|
||||
}
|
||||
|
||||
function findTimings(timings, start, end) {
|
||||
return timings.filter(timing => timing.value > start && timing.value <= end);
|
||||
}
|
||||
module.exports = {
|
||||
getFilmstrip(browsertimeData, run, dir, options) {
|
||||
if (
|
||||
!options.browsertime.video ||
|
||||
options.browsertime.videoParams.createFilmstrip === false
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
let metrics = [];
|
||||
if (browsertimeData) {
|
||||
metrics = getMetricsFromBrowsertime(browsertimeData);
|
||||
}
|
||||
const files = fs.readdirSync(
|
||||
path.join(dir, 'data', 'video', 'images', run + '')
|
||||
);
|
||||
const timings = [];
|
||||
for (let file of files) {
|
||||
timings.push({ time: file.replace(/\D/g, ''), file });
|
||||
}
|
||||
|
||||
const maxTiming = timings.slice(-1)[0].time;
|
||||
const toTheFront = [];
|
||||
// We step 100 ms each step ... but if you wanna show all and the last change is late
|
||||
// use 200 ms
|
||||
const step =
|
||||
maxTiming > 10000 && (options.filmstrip && options.filmstrip.showAll)
|
||||
? 200
|
||||
: 100;
|
||||
let fileName = '';
|
||||
for (let i = 0; i <= Number(maxTiming) + step; i = i + step) {
|
||||
const entry = findFrame(timings, i);
|
||||
const timingMetrics = findTimings(metrics, i - step, i);
|
||||
if (
|
||||
entry.file !== fileName ||
|
||||
timingMetrics.length > 0 ||
|
||||
(options.filmstrip && options.filmstrip.showAll)
|
||||
) {
|
||||
toTheFront.push({
|
||||
time: i / 1000,
|
||||
file: entry.file,
|
||||
timings: timingMetrics
|
||||
});
|
||||
}
|
||||
fileName = entry.file;
|
||||
}
|
||||
return toTheFront;
|
||||
}
|
||||
};
|
||||
|
|
@ -15,6 +15,8 @@ const isEmpty = require('lodash.isempty');
|
|||
const summaryBoxesSetup = require('./setup/summaryBoxes'),
|
||||
detailedSetup = require('./setup/detailed');
|
||||
|
||||
const filmstrip = require('./filmstrip');
|
||||
|
||||
const TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss';
|
||||
|
||||
class HTMLBuilder {
|
||||
|
|
@ -221,6 +223,17 @@ class HTMLBuilder {
|
|||
const pageSummaries = this.pageSummaries.filter(
|
||||
summary => !!get(pageInfo.data, [summary.id, 'pageSummary'])
|
||||
);
|
||||
// We use this for the filmstrip and in the future we should use the data there
|
||||
// as median run all over in the HTML
|
||||
const medianPageInfo = runPages[medianRun.runIndex - 1];
|
||||
let filmstripData = medianPageInfo.data.browsertime
|
||||
? filmstrip.getFilmstrip(
|
||||
medianPageInfo.data.browsertime.run,
|
||||
medianRun.runIndex,
|
||||
this.storageManager.getFullPathToURLDir(url),
|
||||
options
|
||||
)
|
||||
: [];
|
||||
let data = {
|
||||
daurl: url,
|
||||
daurlAlias,
|
||||
|
|
@ -234,6 +247,7 @@ class HTMLBuilder {
|
|||
hasScreenShots: dataCollector.browsertimeScreenshots,
|
||||
screenShotType: dataCollector.browsertimeScreenshotsType,
|
||||
css,
|
||||
filmstrip: filmstripData,
|
||||
h: helpers,
|
||||
JSON: JSON,
|
||||
markdown: markdown,
|
||||
|
|
@ -269,6 +283,15 @@ class HTMLBuilder {
|
|||
this.timestamp
|
||||
);
|
||||
|
||||
const filmstripData = pageInfo.data.browsertime
|
||||
? filmstrip.getFilmstrip(
|
||||
pageInfo.data.browsertime.run,
|
||||
iteration,
|
||||
this.storageManager.getFullPathToURLDir(url),
|
||||
options
|
||||
)
|
||||
: [];
|
||||
|
||||
const pageRuns = this.pageRuns.filter(
|
||||
run => !!get(pageInfo.data, [run.id, 'run'])
|
||||
);
|
||||
|
|
@ -290,6 +313,7 @@ class HTMLBuilder {
|
|||
css,
|
||||
h: helpers,
|
||||
urlLink: './index.html',
|
||||
filmstrip: filmstripData,
|
||||
JSON: JSON,
|
||||
markdown: markdown,
|
||||
rootPath: this.storageManager.rootPathFromUrl(url),
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ module.exports = {
|
|||
this.dataCollector = new DataCollector(context.resultUrls);
|
||||
this.maxAssets = get(options, 'maxAssets', 20);
|
||||
this.alias = {};
|
||||
this.context = context;
|
||||
// we have a couple of default regitered datatypes
|
||||
this.collectDataFrom = [
|
||||
'browsertime.run',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
|
||||
.filmstrip {
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.videoframe {
|
||||
vertical-align: top;
|
||||
display: inline-block;
|
||||
padding: 4px;
|
||||
background-color: #fff;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
width:100%;
|
||||
}
|
||||
|
||||
.videoframe.blue {
|
||||
border: 2px solid $color--blue;
|
||||
}
|
||||
|
||||
.videoframetime {
|
||||
text-align: center;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.videoframetext {
|
||||
text-align: left;
|
||||
display: block;
|
||||
line-height: 1.2em;
|
||||
font-size: 0.8em;
|
||||
white-space: nowrap;
|
||||
margin-bottom: 0.2em;
|
||||
}
|
||||
|
|
@ -43,3 +43,4 @@
|
|||
@import 'components/chartist';
|
||||
@import 'components/chartist-plugin-tooltip';
|
||||
@import 'components/chartistExtras';
|
||||
@import 'components/filmstrip';
|
||||
|
|
|
|||
|
|
@ -0,0 +1,59 @@
|
|||
mixin frame(filmstrip, iteration)
|
||||
- const frameClass = filmstrip[iteration].timings.length > 0 ? 'videoframe blue' : 'videoframe'
|
||||
a(href=path + filmstrip[iteration].file)
|
||||
img(src=path + filmstrip[iteration].file, alt='', class=frameClass)
|
||||
span.videoframetime #{filmstrip[iteration].time} s
|
||||
each timing in filmstrip[iteration].timings
|
||||
span.videoframetext #{timing.name ? timing.name : timing.metric}
|
||||
b #{h.time.ms(timing.value)}
|
||||
|
||||
a
|
||||
h2 Filmstrip
|
||||
|
||||
- const videoIndex = medianRun ? medianRun.runIndex : iteration;
|
||||
- const path = 'data/video/images/' + videoIndex +'/';
|
||||
|
||||
if (options.filmstrip && options.filmstrip.showAll)
|
||||
p
|
||||
| Showing all the filmstrips. Set
|
||||
code --filmstrip.showAll
|
||||
| to false to only show the ones that contains a change.
|
||||
else
|
||||
p
|
||||
| Use
|
||||
code --filmstrip.showAll
|
||||
| to show all filmstrips.
|
||||
|
||||
if filmstrip
|
||||
- for (let i=0; i<filmstrip.length; i=i+4)
|
||||
.row
|
||||
if options.mobile
|
||||
.two.columns.filmstrip
|
||||
+frame(filmstrip, i)
|
||||
if filmstrip[i+1]
|
||||
.two.columns.filmstrip
|
||||
+frame(filmstrip, i+1)
|
||||
if filmstrip[i+2]
|
||||
.two.columns.filmstrip
|
||||
+frame(filmstrip, i+2)
|
||||
if filmstrip[i+3]
|
||||
.two.columns.filmstrip
|
||||
+frame(filmstrip, i+3)
|
||||
if filmstrip[i+4]
|
||||
.two.columns.filmstrip
|
||||
+frame(filmstrip, i+4)
|
||||
if filmstrip[i+5]
|
||||
.two.columns.filmstrip
|
||||
+frame(filmstrip, i+5)
|
||||
else
|
||||
.three.columns.filmstrip
|
||||
+frame(filmstrip, i)
|
||||
if filmstrip[i+1]
|
||||
.three.columns.filmstrip
|
||||
+frame(filmstrip, i+1)
|
||||
if filmstrip[i+2]
|
||||
.three.columns.filmstrip
|
||||
+frame(filmstrip, i+2)
|
||||
if filmstrip[i+3]
|
||||
.three.columns.filmstrip
|
||||
+frame(filmstrip, i+3)
|
||||
|
|
@ -121,6 +121,10 @@ block content
|
|||
section#video-panel
|
||||
include ../video/index.pug
|
||||
|
||||
if options.browsertime.video && options.videoParams.createFilmstrip
|
||||
section#filmstrip-panel
|
||||
include ../filmstrip/index.pug
|
||||
|
||||
if d.coach && d.coach.run
|
||||
section#coach-panel
|
||||
include ../coach/index.pug
|
||||
|
|
|
|||
|
|
@ -9,7 +9,10 @@
|
|||
| Metrics
|
||||
if options.browsertime.video
|
||||
a(id='video', href='#video', onclick='return selectTab(this, true);')
|
||||
| Video
|
||||
| Video
|
||||
if options.browsertime.video && options.videoParams.createFilmstrip
|
||||
a(id='filmstrip', href='#filmstrip', onclick='return selectTab(this, true);')
|
||||
| Filmstrip
|
||||
if d.coach && d.coach.run
|
||||
a(id='coach', href='#coach', onclick='return selectTab(this, true);')
|
||||
| Coach
|
||||
|
|
|
|||
|
|
@ -120,6 +120,10 @@ block content
|
|||
section#video-panel
|
||||
include ../video/index.pug
|
||||
|
||||
if options.browsertime.video && options.videoParams.createFilmstrip
|
||||
section#filmstrip-panel
|
||||
include ../filmstrip/index.pug
|
||||
|
||||
if d.coach && d.coach.pageSummary
|
||||
section#coach-panel
|
||||
include ../coach/index.pug
|
||||
|
|
|
|||
|
|
@ -9,7 +9,10 @@
|
|||
| Metrics
|
||||
if options.browsertime.video
|
||||
a(id='video', href='#video', onclick='return selectTab(this, true);')
|
||||
| Video
|
||||
| Video
|
||||
if options.browsertime.video && options.videoParams.createFilmstrip
|
||||
a(id='filmstrip', href='#filmstrip', onclick='return selectTab(this, true);')
|
||||
| Filmstrip
|
||||
if d.coach && d.coach.pageSummary
|
||||
a(id='coach', href='#coach', onclick='return selectTab(this, true);')
|
||||
| Coach
|
||||
|
|
|
|||
Loading…
Reference in New Issue