diff --git a/goml-script.md b/goml-script.md index bf5dbb3a..dcc88408 100644 --- a/goml-script.md +++ b/goml-script.md @@ -156,6 +156,7 @@ Here's the command list: * [`assert-variable-false`](#assert-variable-false) * [`assert-window-property`](#assert-window-property) * [`assert-window-property-false`](#assert-window-property-false) + * [`block-network-request`](#block-network-request) * [`call-function`](#call-function) * [`click`](#click) * [`click-with-offset`](#click-with-offset) @@ -861,6 +862,26 @@ assert-window-property-false: ( ) ``` +#### block-network-request + +**block-network-request** prevents a URL that matches a glob from loading. Asterisks `*` are wildcards: + +``` +// Prevent search index from loading +block-network-request: "*/search-index.js" +``` + +By default, a failed network request will cause the test to fail. +Use the [`fail-on-request-error`](#fail-on-request-error) to change this. + + * If you use `block-network-request` with `fail-on-request-error` turned on, + which is the default, the test case will fail if the page makes a blocked + network request. It acts as an assertion that the request is not made. + + * To test the page's functionality after the request fails, turn it off: + + fail-on-request-error: false + #### call-function **call-function** command allows you to call a function defined with `define-function`. It expects a tuple containing the name of the function to call and its arguments (if any). Example: diff --git a/src/commands.js b/src/commands.js index 3fa26bda..1400f282 100644 --- a/src/commands.js +++ b/src/commands.js @@ -30,6 +30,7 @@ const ORDERS = { 'assert-variable-false': commands.parseAssertVariableFalse, 'assert-window-property': commands.parseAssertWindowProperty, 'assert-window-property-false': commands.parseAssertWindowPropertyFalse, + 'block-network-request': commands.parseBlockNetworkRequest, 'click': commands.parseClick, 'click-with-offset': commands.parseClickWithOffset, 'call-function': commands.parseCallFunction, diff --git a/src/commands/all.js b/src/commands/all.js index 2f36c1d5..b71fd95a 100644 --- a/src/commands/all.js +++ b/src/commands/all.js @@ -9,6 +9,7 @@ const functions = require('./functions.js'); const general = require('./general.js'); const input = require('./input.js'); const navigation = require('./navigation.js'); +const network = require('./network.js'); const store = require('./store.js'); const wait = require('./wait.js'); @@ -37,6 +38,7 @@ module.exports = { 'parseAssertVariableFalse': assert.parseAssertVariableFalse, 'parseAssertWindowProperty': assert.parseAssertWindowProperty, 'parseAssertWindowPropertyFalse': assert.parseAssertWindowPropertyFalse, + 'parseBlockNetworkRequest': network.parseBlockNetworkRequest, 'parseCallFunction': functions.parseCallFunction, 'parseClick': input.parseClick, 'parseClickWithOffset': input.parseClickWithOffset, diff --git a/src/commands/network.js b/src/commands/network.js new file mode 100644 index 00000000..5d267ef6 --- /dev/null +++ b/src/commands/network.js @@ -0,0 +1,57 @@ +// List commands handling network blocking. + +const { validator } = require('../validator.js'); +// Not the same `utils.js`! +const { hasError } = require('../utils.js'); + +// Input: glob (for example: "*.js") +function parseBlockNetworkRequest(parser) { + const ret = validator(parser, { + kind: 'string', + allowEmpty: false, + }); + if (hasError(ret)) { + return ret; + } + const glob = ret.value.value.trim(); + + return { + 'instructions': [ + `await page.setRequestInterception(true); + page.on('request', interceptedRequest => { + if (interceptedRequest.isInterceptResolutionHandled()) return; + function matchesGlob(glob, text) { + const wildcard = glob.indexOf("*"); + if (wildcard === -1) { + return glob === text; + } + const prefixGlob = glob.substring(0, wildcard); + const prefixText = text.substring(0, wildcard); + if (prefixGlob !== prefixText) { + return false; + } + const suffixGlob = glob.substring(wildcard + 1); + let suffixText = text.substring(wildcard); + if (suffixGlob.indexOf("*") === -1) { + return suffixText.endsWith(suffixGlob); + } + let matched = matchesGlob(suffixGlob, suffixText); + while (suffixText !== "" && !matched) { + suffixText = suffixText.substring(1); + matched = matchesGlob(suffixGlob, suffixText); + } + return matched; + } + if (matchesGlob("${glob}", interceptedRequest.url())) { + interceptedRequest.abort(); + } else { + interceptedRequest.continue({}, 0); + } + });`, + ], + }; +} + +module.exports = { + 'parseBlockNetworkRequest': parseBlockNetworkRequest, +}; diff --git a/tests/api-output/parseBlockNetworkRequest/err-float.toml b/tests/api-output/parseBlockNetworkRequest/err-float.toml new file mode 100644 index 00000000..e39cd9f8 --- /dev/null +++ b/tests/api-output/parseBlockNetworkRequest/err-float.toml @@ -0,0 +1 @@ +error = """expected a string, found `1.1` (a number)""" diff --git a/tests/api-output/parseBlockNetworkRequest/err-int.toml b/tests/api-output/parseBlockNetworkRequest/err-int.toml new file mode 100644 index 00000000..9a2b7767 --- /dev/null +++ b/tests/api-output/parseBlockNetworkRequest/err-int.toml @@ -0,0 +1 @@ +error = """expected a string, found `1` (a number)""" diff --git a/tests/api-output/parseBlockNetworkRequest/err-quote-2.toml b/tests/api-output/parseBlockNetworkRequest/err-quote-2.toml new file mode 100644 index 00000000..7fa81047 --- /dev/null +++ b/tests/api-output/parseBlockNetworkRequest/err-quote-2.toml @@ -0,0 +1 @@ +error = """expected `\"` at the end of the string""" diff --git a/tests/api-output/parseBlockNetworkRequest/err-quote.toml b/tests/api-output/parseBlockNetworkRequest/err-quote.toml new file mode 100644 index 00000000..7fa81047 --- /dev/null +++ b/tests/api-output/parseBlockNetworkRequest/err-quote.toml @@ -0,0 +1 @@ +error = """expected `\"` at the end of the string""" diff --git a/tests/api-output/parseBlockNetworkRequest/err-tuple-2.toml b/tests/api-output/parseBlockNetworkRequest/err-tuple-2.toml new file mode 100644 index 00000000..897a4fe2 --- /dev/null +++ b/tests/api-output/parseBlockNetworkRequest/err-tuple-2.toml @@ -0,0 +1 @@ +error = """expected a string, found `(\"a\", 2)` (a tuple)""" diff --git a/tests/api-output/parseBlockNetworkRequest/err-tuple-3.toml b/tests/api-output/parseBlockNetworkRequest/err-tuple-3.toml new file mode 100644 index 00000000..c2c307b0 --- /dev/null +++ b/tests/api-output/parseBlockNetworkRequest/err-tuple-3.toml @@ -0,0 +1 @@ +error = """expected a string, found `()` (a tuple)""" diff --git a/tests/api-output/parseBlockNetworkRequest/err-tuple-4.toml b/tests/api-output/parseBlockNetworkRequest/err-tuple-4.toml new file mode 100644 index 00000000..1b1aeba7 --- /dev/null +++ b/tests/api-output/parseBlockNetworkRequest/err-tuple-4.toml @@ -0,0 +1 @@ +error = """expected a string, found `(\"x\")` (a tuple)""" diff --git a/tests/api-output/parseBlockNetworkRequest/err-tuple.toml b/tests/api-output/parseBlockNetworkRequest/err-tuple.toml new file mode 100644 index 00000000..b74e8d68 --- /dev/null +++ b/tests/api-output/parseBlockNetworkRequest/err-tuple.toml @@ -0,0 +1 @@ +error = """expected a string, found `(a, \"b\")` (a tuple)""" diff --git a/tests/api-output/parseBlockNetworkRequest/ok.toml b/tests/api-output/parseBlockNetworkRequest/ok.toml new file mode 100644 index 00000000..da60573a --- /dev/null +++ b/tests/api-output/parseBlockNetworkRequest/ok.toml @@ -0,0 +1,33 @@ +instructions = [ + """await page.setRequestInterception(true); + page.on('request', interceptedRequest => { + if (interceptedRequest.isInterceptResolutionHandled()) return; + function matchesGlob(glob, text) { + const wildcard = glob.indexOf(\"*\"); + if (wildcard === -1) { + return glob === text; + } + const prefixGlob = glob.substring(0, wildcard); + const prefixText = text.substring(0, wildcard); + if (prefixGlob !== prefixText) { + return false; + } + const suffixGlob = glob.substring(wildcard + 1); + let suffixText = text.substring(wildcard); + if (suffixGlob.indexOf(\"*\") === -1) { + return suffixText.endsWith(suffixGlob); + } + let matched = matchesGlob(suffixGlob, suffixText); + while (suffixText !== \"\" && !matched) { + suffixText = suffixText.substring(1); + matched = matchesGlob(suffixGlob, suffixText); + } + return matched; + } + if (matchesGlob(\"x\", interceptedRequest.url())) { + interceptedRequest.abort(); + } else { + interceptedRequest.continue({}, 0); + } + });""", +] diff --git a/tests/html_files/external-script.html b/tests/html_files/external-script.html new file mode 100644 index 00000000..b8344994 --- /dev/null +++ b/tests/html_files/external-script.html @@ -0,0 +1,2 @@ + + diff --git a/tests/html_files/external-script.js b/tests/html_files/external-script.js new file mode 100644 index 00000000..77557433 --- /dev/null +++ b/tests/html_files/external-script.js @@ -0,0 +1 @@ +document.write("
"); diff --git a/tests/ui/block-network-request-multiple-star.goml b/tests/ui/block-network-request-multiple-star.goml new file mode 100644 index 00000000..1d718f49 --- /dev/null +++ b/tests/ui/block-network-request-multiple-star.goml @@ -0,0 +1,13 @@ +// This test is meant to ensure that we can block an external script request. +// Since we are intentionally causing request errors, don't block them. +fail-on-request-error: false +go-to: "file://" + |CURRENT_DIR| + "/" + |DOC_PATH| + "/external-script.html" +assert: "#output" +// This does not match, so it won't cause a fail. +block-network-request: "*s6ojwNhzwbj9F2cYwA9tqjtnFyUzt6YhQzfviRjB*.js" +reload: +assert: "#output" +// This matches, so the script won't load +block-network-request: "*extern*.js" +reload: +assert-false: "#output" diff --git a/tests/ui/block-network-request-multiple-star.output b/tests/ui/block-network-request-multiple-star.output new file mode 100644 index 00000000..86dc0468 --- /dev/null +++ b/tests/ui/block-network-request-multiple-star.output @@ -0,0 +1,5 @@ +=> Starting doc-ui tests... + +block-network-request-multiple-star... OK + +<= doc-ui tests done: 1 succeeded, 0 failed \ No newline at end of file diff --git a/tests/ui/block-network-request.goml b/tests/ui/block-network-request.goml new file mode 100644 index 00000000..b5e95fab --- /dev/null +++ b/tests/ui/block-network-request.goml @@ -0,0 +1,13 @@ +// This test is meant to ensure that we can block an external script request. +// Since we are intentionally causing request errors, don't block them. +fail-on-request-error: false +go-to: "file://" + |CURRENT_DIR| + "/" + |DOC_PATH| + "/external-script.html" +assert: "#output" +// This does not match, so it won't cause a fail. +block-network-request: "*s6ojwNhzwbj9F2cYwA9tqjtnFyUzt6YhQzfviRjB.js" +reload: +assert: "#output" +// This matches, so the script won't load +block-network-request: "*.js" +reload: +assert-false: "#output" diff --git a/tests/ui/block-network-request.output b/tests/ui/block-network-request.output new file mode 100644 index 00000000..63d85b68 --- /dev/null +++ b/tests/ui/block-network-request.output @@ -0,0 +1,5 @@ +=> Starting doc-ui tests... + +block-network-request... OK + +<= doc-ui tests done: 1 succeeded, 0 failed \ No newline at end of file diff --git a/tools/api.js b/tools/api.js index 9c2ea13e..c25eb2e4 100644 --- a/tools/api.js +++ b/tools/api.js @@ -273,6 +273,18 @@ function checkAssertObjProperty(x, func) { func('({"a"."b": null}, CONTAINS)', 'object-path-4'); } +function checkBlockNetworkRequest(x, func) { + func('"', 'err-quote'); + func('"x', 'err-quote-2'); + func('1', 'err-int'); + func('1.1', 'err-float'); + func('(a, "b")', 'err-tuple'); + func('("a", 2)', 'err-tuple-2'); + func('()', 'err-tuple-3'); + func('("x")', 'err-tuple-4'); + func('"x"', 'ok'); +} + function checkAssertVariable(x, func) { func('', 'err-1'); func('hello', 'err-2'); @@ -2447,6 +2459,11 @@ const TO_CHECK = [ return wrapper(parserFuncs.parseAssertWindowPropertyFalse, x, e, name, o); }, }, + { + 'name': 'block-network-request', + 'func': checkBlockNetworkRequest, + 'toCall': (x, e, name, o) => wrapper(parserFuncs.parseBlockNetworkRequest, x, e, name, o), + }, { 'name': 'set-attribute', 'func': checkAttributeProperty,