Skip to content

Commit de03bcc

Browse files
Added TestandWait for CLI & Script
1 parent a8f52eb commit de03bcc

File tree

3 files changed

+210
-4
lines changed

3 files changed

+210
-4
lines changed

README.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ $ webpagetest --help
6363
- **locations** _[options]_: list locations and the number of pending tests
6464
- **testers** _[options]_: list testers status and details
6565
- **test** _[options] \<url_or_script\>_: run test, _\<url_or_script\>_ can also be a path to a script file
66+
- **testAndWait** _[options] \<url_or_script\>_: run test and waits for the result, _\<url_or_script\>_ can also be a path to a script file
6667
- **testBalance** _[options]_: get remaining tests for the account
6768
- **restart** _\<id\>_: restart test
6869
- **cancel** _\<id\>_: cancel running/pending test
@@ -448,6 +449,7 @@ Methods and options (including the one letter shorthands) are the same when usin
448449
- `getTesters(options, callback)`
449450
- `getTestBalance(options, callback)`
450451
- `runTest(url_or_script, options, callback)`
452+
- `runTestAndWait(url_or_script, options, callback)`
451453
- `restartTest(id, options, callback)`
452454
- `cancelTest(id, options, callback)`
453455
- `getHARData(id, options, callback)`
@@ -516,7 +518,7 @@ wpt.runTest(script, (err, data) => {
516518
- **dryRun**: _Boolean_, if `true`, method does not make an actual request to the API Server but rather returns an object with `url` which contains the actual URL to make the GET request to WebPageTest API Server
517519
- **server**: _String_, if specified, overrides the WebPageTest server informed in the constructor only for that method call
518520

519-
#### Test (works for `runTest` method only)
521+
#### Test (works with `runTest` and `runTestAndWait`)
520522

521523
- **location**: _String_, location to test from
522524
- **connectivity**: _String_, connectivity profile -- requires location to be specified -- (Cable|DSL|3GSlow|3G|3GFast|4G|LTE|Edge|2G|Dial|FIOS|Native|custom) [Cable]
@@ -590,15 +592,15 @@ wpt.runTest(script, (err, data) => {
590592
- **lighthouse**: _Boolean_, perform lighthouse test (Chrome only, Linux agent only)
591593
- **throttleCPU**: _Number_, custom cpu throttling
592594

593-
#### API Key (works for `runTest`, `restartTest` and `cancelTest` methods)
595+
#### API Key (works for `runTest`, `runTestAndWait`, `restartTest` and `cancelTest` methods)
594596

595597
- **key**: _String_, API key (if assigned). Contact the WebPageTest server administrator for a key if required
596598

597-
#### Request (works for `getTestStatus` `getResults` `getLocations` `getTesters` and `runTest` methods)
599+
#### Request (works for `getTestStatus` `getResults` `getLocations` `getTesters` `runTest` and `runTestAndWait` methods)
598600

599601
- **requestId**: _String_, echo request ID, useful to track asynchronous requests
600602

601-
#### Results (works for `getResults` and `runTest` methods)
603+
#### Results (works for `getResults` `runTest` and `runTestAndWait` methods)
602604

603605
- **breakDown**: _Boolean_, include the breakdown of requests and bytes by mime type
604606
- **domains**: _Boolean_, include the breakdown of requests and bytes by domain

lib/mapping.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -814,6 +814,13 @@ var commands = {
814814
info: "run test",
815815
nokey: [options.results],
816816
},
817+
testAndWait: {
818+
name: "runTestAndWait",
819+
param: "url_or_script",
820+
options: [options.apikey, options.test, options.request, options.results],
821+
info: "run test and waits for the result",
822+
nokey: [options.results],
823+
},
817824
restart: {
818825
name: "restartTest",
819826
param: "id",

lib/webpagetest.js

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -530,6 +530,202 @@ function runTest(what, options, callback) {
530530
return api.call(this, paths.test, callback, query, options);
531531
}
532532

533+
function runTestAndWait(what, options, callback) {
534+
delete options.pollResults;
535+
delete options.timeout;
536+
options = Object.assign(options, { pollResults: 10 });
537+
538+
var query = {};
539+
540+
callback = callback || options;
541+
options = options === callback ? {} : helper.deepClone(options);
542+
543+
// testing url or script?
544+
query[reSpace.test(what) ? "script" : "url"] = what;
545+
// set dummy url when scripting, needed when webdriver script
546+
if (query.script) {
547+
query.url = "https://www.webpagetest.org";
548+
}
549+
helper.setQuery(mapping.commands.test, options, query);
550+
551+
// connectivity
552+
if (reConnectivity.test(options.connectivity) && query.location) {
553+
query.location += "." + options.connectivity;
554+
}
555+
556+
// json output format
557+
query.f = "json";
558+
559+
// API key
560+
if (!query.k && this.config.key) {
561+
query.k = this.config.key;
562+
}
563+
564+
// synchronous tests with results
565+
var testId,
566+
polling,
567+
server,
568+
listen,
569+
timerout,
570+
resultsOptions = {};
571+
572+
function resultsCallback(err, data) {
573+
clearTimeout(timerout);
574+
if (options.exitOnResults) {
575+
process.exit(err);
576+
} else {
577+
callback(err, data);
578+
}
579+
}
580+
581+
function poll(err, data) {
582+
// poll again when test started but not complete
583+
// and not when specs are done testing
584+
if (
585+
!err &&
586+
(!data || (data && data.data && data.statusCode !== 200)) &&
587+
!(typeof err === "number" && data === undefined)
588+
) {
589+
console.log(
590+
data && data.data && data.data.statusText
591+
? data.data.statusText
592+
: "Testing in progress"
593+
);
594+
polling = setTimeout(
595+
getTestResults.bind(this, testId, resultsOptions, poll.bind(this)),
596+
options.pollResults
597+
);
598+
} else {
599+
if (!data) {
600+
data = { testId: testId };
601+
}
602+
resultsCallback(err, data);
603+
}
604+
}
605+
606+
function testCallback(cb, err, data) {
607+
if (err || !(data && data.data && data.data.testId)) {
608+
return callback(err || data);
609+
}
610+
611+
testId = data.data.testId;
612+
613+
if (options.timeout) {
614+
timerout = setTimeout(timeout, options.timeout);
615+
}
616+
617+
if (cb) {
618+
cb.call(this);
619+
}
620+
}
621+
622+
function timeout() {
623+
if (server) {
624+
server.close();
625+
}
626+
clearTimeout(polling);
627+
callback({
628+
error: {
629+
code: "TIMEOUT",
630+
testId: testId,
631+
message: "timeout",
632+
},
633+
});
634+
}
635+
636+
function listener() {
637+
query.pingback = url.format({
638+
protocol: "http",
639+
hostname: options.waitResults.hostname,
640+
port: options.waitResults.port,
641+
pathname: "/testdone",
642+
});
643+
644+
api.call(this, paths.test, testCallback.bind(this, null), query, options);
645+
}
646+
647+
function wait() {
648+
server.listen(options.waitResults.port, listen);
649+
return options.waitResults;
650+
}
651+
652+
// poll|wait results timeout
653+
if (options.timeout) {
654+
options.timeout = (parseInt(options.timeout, 10) || 0) * 1000;
655+
}
656+
657+
// poll|wait results options
658+
Object.keys(mapping.options.results).forEach(function resultsOpts(key) {
659+
var name = mapping.options.results[key].name,
660+
value = options[name] || options[key];
661+
662+
if (value !== undefined) {
663+
resultsOptions[name] = value;
664+
}
665+
});
666+
667+
// poll results
668+
if (options.pollResults && !options.dryRun) {
669+
options.pollResults = parseInt(options.pollResults * 1000, 10) || 5000;
670+
671+
return api.call(
672+
this,
673+
paths.test,
674+
testCallback.bind(this, poll),
675+
query,
676+
options
677+
);
678+
}
679+
680+
// wait results
681+
if (options.waitResults && !options.dryRun) {
682+
options.waitResults = helper.localhost(
683+
options.waitResults,
684+
WebPageTest.defaultWaitResultsPort
685+
);
686+
687+
listen = listener.bind(this);
688+
689+
server = http.createServer(
690+
function (req, res) {
691+
var uri = url.parse(req.url, true);
692+
693+
res.statusCode = 204;
694+
res.end();
695+
696+
if (uri.pathname === "/testdone" && uri.query.id === testId) {
697+
server.close(
698+
getTestResults.bind(
699+
this,
700+
uri.query.id,
701+
resultsOptions,
702+
resultsCallback
703+
)
704+
);
705+
}
706+
}.bind(this)
707+
);
708+
709+
server.on(
710+
"error",
711+
function (err) {
712+
if (["EACCES", "EADDRINUSE"].indexOf(err.code) > -1) {
713+
// remove old unused listener and bump port for next attempt
714+
server.removeListener("listening", listen);
715+
options.waitResults.port++;
716+
wait.call(this);
717+
} else {
718+
callback(err);
719+
}
720+
}.bind(this)
721+
);
722+
723+
return wait.call(this);
724+
}
725+
726+
return api.call(this, paths.test, callback, query, options);
727+
}
728+
533729
function restartTest(id, options, callback) {
534730
var query = { resubmit: id };
535731

@@ -932,6 +1128,7 @@ WebPageTest.prototype = {
9321128
getLocations: getLocations,
9331129
getTesters: getTesters,
9341130
runTest: runTest,
1131+
runTestAndWait: runTestAndWait,
9351132
restartTest: restartTest,
9361133
cancelTest: cancelTest,
9371134
getPageSpeedData: getPageSpeedData,

0 commit comments

Comments
 (0)