Skip to content

Commit db98efd

Browse files
authored
Action structure validation (#61)
* Validate action * dependencies * dependencies * typo
1 parent 29ca65c commit db98efd

17 files changed

+79
-57
lines changed

actions/action.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ exports.action = async function action(page, request) {
2424
throw new exceptions.IncorrectArgumentError("Invalid action function.\n" +
2525
"Valid action function: \"async function action(page, request) " +
2626
"{ ... some actions with request and page in puppeteer " +
27-
"syntax};\"");
27+
"syntax};\".");
2828
}
2929

3030
return {

actions/click.js

+12-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
const utils = require('../helpers/utils');
2+
const exceptions = require('../helpers/exceptions');
23

34
const DEFAULT_TIMEOUT = 1000; // 1 second
45

5-
/*
6+
/**
67
* body = {
78
* "selector": "", // <string> A selector to search for element to click. If there are multiple elements satisfying the selector, the first will be clicked.
89
* "clickOptions": {
@@ -11,10 +12,19 @@ const DEFAULT_TIMEOUT = 1000; // 1 second
1112
* "delay" // <number> Time to wait between mousedown and mouseup in milliseconds. Defaults to 0.
1213
* },
1314
* "waitOptions": {...}, // same as in goto action, defaults to 1s timeout
14-
* "navigationOptions": {...} // same as in goto action
15+
* "navigationOptions": {...}, // same as in goto action
1516
* }
1617
*/
1718
exports.click = async function click(page, request) {
19+
// Validation
20+
if (!("selector" in request.body)) {
21+
throw new exceptions.IncorrectArgumentError("No selector to click in request.");
22+
}
23+
if (typeof request.body.selector != "string") {
24+
throw new exceptions.IncorrectArgumentError("Selector should be a string.");
25+
}
26+
27+
// Evaluation
1828
await page.hover(request.body.selector);
1929
if (request.body.navigationOptions) {
2030
await Promise.all([

actions/compose.js

+22
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
const exceptions = require("../helpers/exceptions");
2+
13
endpoint2action = {
24
action: require("./action").action,
35
click: require("./click").click,
@@ -12,7 +14,27 @@ endpoint2action = {
1214
scroll: require("./scroll").scroll,
1315
}
1416

17+
/**
18+
* Compose is a sequence of other actions that are performed on the page.
19+
* The function does not allow body to contain compose endpoint inside.
20+
*
21+
* body = {
22+
* "actions": [ An array of actions
23+
* {
24+
* "endpoint": <string>, Action endpoint
25+
* "body": <object>, A body that is processable by action on the endpoint
26+
* },
27+
* ...,
28+
* ],
29+
* }
30+
*/
1531
async function compose(page, request) {
32+
// Validation
33+
if (!(request.body instanceof Object)) {
34+
throw new exceptions.IncorrectArgumentError("Body of compose method should be an Object.");
35+
}
36+
37+
// Evaluation
1638
const originalClosePage = request.query.closePage;
1739
const originalBody = structuredClone(request.body);
1840

actions/fill_form.js

+14-8
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,23 @@
11
const utils = require('../helpers/utils');
2+
const exceptions = require("../helpers/exceptions");
23

3-
/*
4+
/**
45
* body = {
5-
* "inputMapping": { A dictionary where each key is a CSS selector, and each value is another dictionary containing details about the input for that element:
6-
* "selector": <string> The CSS selector for the input element (used as the key).
7-
* "value": <string> The text to be inputted into the element.
8-
* "delay": <number> A delay (in milliseconds) between each keystroke when inputting the text. Defaults to 0 if not provided.
6+
* "inputMapping": { A dictionary where each key is a CSS selector, and each value is another dictionary containing details about the input for that element:
7+
* "selector": <string>, The CSS selector for the input element (used as the key).
8+
* "value": <string>, The text to be inputted into the element.
9+
* "delay": <number>, A delay (in milliseconds) between each keystroke when inputting the text. Defaults to 0 if not provided.
910
* },
10-
* "submitButton": <string> The CSS selector for the form's submit button. If provided, the button will be clicked after filling in the form.
11+
* "submitButton": <string>, The CSS selector for the form's submit button. If provided, the button will be clicked after filling in the form.
1112
* }
1213
*/
1314
exports.fillForm = async function fillForm(page, request) {
15+
// Validation
16+
if (!("inputMapping" in request.body)) {
17+
throw new exceptions.IncorrectArgumentError("No inputMapping provided in fill_form request.");
18+
}
19+
20+
// Evaluation
1421
const inputMapping = request.body.inputMapping;
1522
const submitButton = request.body.submitButton;
1623

@@ -20,10 +27,9 @@ exports.fillForm = async function fillForm(page, request) {
2027
await page.type(selector, value, { delay });
2128
}
2229

23-
if (submitButton) {
30+
if (submitButton) { // Maybe we can use click action here?
2431
await page.click(submitButton);
2532
}
2633

2734
return await utils.getContents(page);
28-
2935
}

actions/goto.js

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
const utils = require('../helpers/utils');
2+
const exceptions = require('../helpers/exceptions');
23

3-
/*
4+
/**
45
* body = {
56
* "url": <string> URL to navigate page to. The url should include scheme, e.g. https://.
67
* "navigationOptions": { Navigation parameters which might have the following properties:
@@ -22,6 +23,11 @@ const utils = require('../helpers/utils');
2223
* }
2324
*/
2425
exports.goto = async function goto(page, request) {
26+
// Validation
27+
if (!("url" in request.body)) {
28+
throw new exceptions.IncorrectArgumentError("No URL provided in goto request.");
29+
}
30+
2531
await page.goto(request.body.url, request.body.navigationOptions);
2632
return await utils.getContents(page, request.body.waitOptions);
2733
}

actions/har.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
const exceptions = require("../helpers/exceptions");
22

3+
/**
4+
* Captures har of the page.
5+
*/
36
exports.har = async function har(page, request) {
4-
if (!(page.harWriter)){
7+
if (!(page.harWriter)) {
58
throw new exceptions.NoHarWriterError();
69
}
710

actions/mhtml.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
/*
2-
* Captures mhtml snapshot of a page
1+
/**
2+
* Captures mhtml snapshot of the page.
33
*/
44
exports.captureSnapshot = async function captureSnapshot(page, request) {
55
const cdpSession = await page.target().createCDPSession();

actions/recaptcha_solver.js

+14-10
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,41 @@
11
const utils = require('../helpers/utils')
2+
const exceptions = require("../helpers/exceptions");
23

34
const DEFAULT_TIMEOUT = 1000; // 1 second
45

5-
/*
6-
* This module introduces new ability to puppeteer-service.
7-
* It is capable of solving recaptchas on a given web-page.
8-
* If there is no recaptcha on the page nothing bad will happen.
9-
* If there is recaptcha it solves it and then inserts the special code
6+
/**
7+
* The function solves recaptchas on the page.
8+
* If there is no recaptcha on the page nothing will happen.
9+
* If there is a recaptcha the function solves it and then inserts the special code
1010
* into the page automatically.
1111
*
1212
* Returns useful information about recaptcha_solving.
1313
* For more information about return value visit
1414
* https://github.com/berstend/puppeteer-extra/tree/master/packages/puppeteer-extra-plugin-recaptcha#result-object
1515
*/
1616
exports.recaptchaSolver = async function recaptchaSolver(page, request) {
17-
let recaptcha_data;
17+
if (!("solve_recaptcha" in request.body)) {
18+
throw new exceptions.IncorrectArgumentError("No solve_recaptcha parameter in request");
19+
}
20+
21+
let recaptchaData;
1822

1923
if (request.body.solve_recaptcha) {
20-
recaptcha_data = await page.solveRecaptchas();
24+
recaptchaData = await page.solveRecaptchas();
2125
} else {
22-
recaptcha_data = await page.findRecaptchas();
26+
recaptchaData = await page.findRecaptchas();
2327
}
2428

2529
const waitOptions = request.body.waitOptions || { timeout: DEFAULT_TIMEOUT };
2630
const contents = await utils.getContents(page, waitOptions);
2731

2832
if (request.query.closePage ||
29-
(request.body.close_on_empty && recaptcha_data['captchas'].length === 0)) {
33+
(request.body.close_on_empty && recaptchaData['captchas'].length === 0)) {
3034
await page.close();
3135
}
3236

3337
return {
3438
...contents,
35-
recaptcha_data: recaptcha_data,
39+
recaptcha_data: recaptchaData,
3640
}
3741
}

actions/screenshot.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/*
1+
/**
22
* Method that returns screenshots of pages
33
* more description of options you can see on GitHub:
44
* https://github.com/GoogleChrome/puppeteer/blob/v1.19.0/docs/api.md#pagescreenshotoptions

actions/scroll.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ const utils = require('../helpers/utils');
22

33
const DEFAULT_TIMEOUT = 1000; // 1 second
44

5-
/*
5+
/**
66
* Method that scrolls page to a certain selector.
77
* Example body:
88
* body = {

helpers/middlewares/process_exception.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ exports.processExceptionMiddleware = async function processExceptionMiddleware(e
2020
res.status(400);
2121
} else if (err instanceof exceptions.NoHarWriterError) {
2222
res.status(400);
23-
}else if (err instanceof exceptions.TooManyContextsError) {
23+
} else if (err instanceof exceptions.TooManyContextsError) {
2424
res.status(429); // Too Many Requests
2525
} else if (err.contextId) { // there was a context, but something went wrong
2626
res.status(500);

helpers/utils.js

-1
Original file line numberDiff line numberDiff line change
@@ -234,4 +234,3 @@ exports.performAction = async function performAction(request, action) {
234234
return response;
235235
});
236236
};
237-

routes/click.js

-5
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,10 @@ const express = require('express');
22

33
const {click} = require('../actions/click');
44
const utils = require('../helpers/utils');
5-
const exceptions = require('../helpers/exceptions');
65

76
const router = express.Router();
87

98
router.post('/', async function (req, res, next) {
10-
if (!("selector" in req.body)) {
11-
throw new exceptions.IncorrectArgumentError("No selector to click in request");
12-
}
13-
149
try {
1510
let response = await utils.performAction(req, click);
1611
res.header('scrapy-puppeteer-service-context-id', response.contextId);

routes/compose.js

-5
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,11 @@
11
const express = require('express');
22

33
const {compose} = require('../actions/compose');
4-
const exceptions = require('../helpers/exceptions');
54
const utils = require("../helpers/utils");
65

76
const router = express.Router();
87

98
router.post('/', async function (req, res, next){
10-
if (!(req.body instanceof Object)) {
11-
throw new exceptions.IncorrectArgumentError("Body of compose method should be an Object");
12-
}
13-
149
try {
1510
let response = await utils.performAction(req, compose);
1611
res.header('scrapy-puppeteer-service-context-id', response.contextId);

routes/fill_form.js

-5
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,10 @@ const express = require('express');
22

33
const {fillForm} = require('../actions/fill_form');
44
const utils = require('../helpers/utils');
5-
const exceptions = require('../helpers/exceptions');
65

76
const router = express.Router();
87

98
router.post('/', async function (req, res, next) {
10-
if (!req.body.inputMapping) {
11-
throw new exceptions.IncorrectArgumentError("No inputMapping provided in fill_form request");
12-
}
13-
149
try {
1510
let response = await utils.performAction(req, fillForm);
1611
res.header('scrapy-puppeteer-service-context-id', response.contextId);

routes/goto.js

-5
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,10 @@ const express = require('express');
22

33
const {goto} = require('../actions/goto');
44
const utils = require('../helpers/utils');
5-
const exceptions = require('../helpers/exceptions');
65

76
const router = express.Router();
87

98
router.post('/', async function (req, res, next) {
10-
if (!req.body.url) {
11-
throw new exceptions.IncorrectArgumentError("No URL provided in goto request");
12-
}
13-
149
try {
1510
let response = await utils.performAction(req, goto);
1611
res.header('scrapy-puppeteer-service-context-id', response.contextId);

routes/recaptcha_solver.js

-8
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,13 @@ const utils = require('../helpers/utils')
66
const exceptions = require('../helpers/exceptions');
77

88
router.post('/', async function (req, res, next) {
9-
if (!req.query.contextId || !req.query.pageId) {
10-
throw new exceptions.IncorrectArgumentError("No page in request");
11-
}
12-
139
if (!process.env.TOKEN_2CAPTCHA) {
1410
res.status(501);
1511
res.send("TOKEN_2CAPTCHA is not provided!");
1612
next();
1713
return;
1814
}
1915

20-
if (!("solve_recaptcha" in req.body)) {
21-
throw new exceptions.IncorrectArgumentError("No solve_recaptcha parameter in request");
22-
}
23-
2416
try {
2517
let response = await utils.performAction(req, recaptchaSolver);
2618
res.header('scrapy-puppeteer-service-context-id', response.contextId);

0 commit comments

Comments
 (0)