import { ElementHandle, EvaluateFunc, HTTPRequest, HTTPResponse, Page, WaitForOptions, WaitTimeoutOptions } from 'puppeteer-core'; import { Prototype } from '../../../typings/chrome-aws-lambda'; let Super: Prototype = null; try { Super = require('puppeteer/lib/cjs/puppeteer/common/ElementHandle.js').ElementHandle; } catch (error) { Super = require('puppeteer-core/lib/cjs/puppeteer/common/ElementHandle.js').ElementHandle; } Super.prototype.clear = function () { return this.click({ clickCount: 3 }).then(() => this.press('Backspace')); }; Super.prototype.clickAndWaitForNavigation = function (options?: WaitForOptions) { options = options ?? { waitUntil: [ 'load', ], }; let promises: [Promise, Promise] = [ ((this as any)._page as Page).waitForNavigation(options), this.click(), ]; return Promise.all(promises).then((value) => value.shift() as HTTPResponse); }; Super.prototype.clickAndWaitForRequest = function (predicate: string | RegExp | ((request: HTTPRequest) => boolean | Promise), options?: WaitTimeoutOptions) { let callback = (request: HTTPRequest) => { let url = request.url(); if (typeof predicate === 'string' && predicate.includes('*') === true) { predicate = new RegExp(predicate.replace(/[-\/\\^$+?.()|[\]{}]/g, '\\$&').replace(/[*]+/g, '.*?'), 'g'); } if (predicate instanceof RegExp) { return predicate.test(url); } return predicate === url; }; let promises: [Promise, Promise] = [ ((this as any)._page as Page).waitForRequest((typeof predicate === 'function') ? predicate : callback, options), this.click(), ]; return Promise.all(promises).then((value) => value.shift() as HTTPRequest); }; Super.prototype.clickAndWaitForResponse = function (predicate: string | RegExp | ((request: HTTPResponse) => boolean | Promise), options?: WaitTimeoutOptions) { let callback = (request: HTTPResponse) => { let url = request.url(); if (typeof predicate === 'string' && predicate.includes('*') === true) { predicate = new RegExp(predicate.replace(/[-\/\\^$+?.()|[\]{}]/g, '\\$&').replace(/[*]+/g, '.*?'), 'g'); } if (predicate instanceof RegExp) { return predicate.test(url); } return predicate === url; }; let promises: [Promise, Promise] = [ ((this as any)._page as Page).waitForResponse((typeof predicate === 'function') ? predicate : callback, options), this.click(), ]; return Promise.all(promises).then((value) => value.shift() as HTTPResponse); }; Super.prototype.fillFormByLabel = function >(data: T) { let callback = (node: HTMLFormElement, data: T) => { if (node.nodeName.toLowerCase() !== 'form') { throw new Error('Element is not a
element.'); } let result: Record = {}; for (let [key, value] of Object.entries(data)) { let selector = [ `id(string(//label[normalize-space(.) = "${key}"]/@for))`, `//label[normalize-space(.) = "${key}"]//*[self::input or self::select or self::textarea]`, ].join(' | '); if (result.hasOwnProperty(key) !== true) { result[key] = []; } let element: Node = null; let elements: HTMLInputElement[] = []; let iterator = document.evaluate(selector, node, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null); while ((element = iterator.iterateNext()) != null) { elements.push(element as HTMLInputElement); } if (elements.length === 0) { throw new Error(`No elements match the selector '${selector}' for '${key}'.`); } let type = (elements[0].getAttribute('type') || elements[0].nodeName).toLowerCase(); let values: (boolean | string)[] = (Array.isArray(value) === true) ? value as (boolean | string)[] : [value] as (boolean | string)[]; if (type === 'file') { throw new Error(`Input element of type 'file' is not supported.`); } for (let element of elements) { try { element.focus(); element.dispatchEvent(new Event('focus')); } catch (error) { } if (type === 'select') { element.value = undefined; for (let index of ['value', 'label'] as ['value', 'label']) { if (result[key].length > 0) { break; } for (let option of Array.from((element as unknown as HTMLSelectElement).options)) { option.selected = values.includes(option[index]); if (option.selected === true) { result[key].push(option.value); if (element.multiple !== true) { break; } } } } } else if (type === 'checkbox' || type === 'radio') { element.checked = (value === true) || values.includes(element.value); if (element.checked === true) { result[key].push(element.value); } } else if (typeof value === 'string') { if (element.isContentEditable === true) { result[key].push(element.textContent = value); } else { result[key].push(element.value = value); } } for (let trigger of ['input', 'change']) { element.dispatchEvent(new Event(trigger, { 'bubbles': true })); } try { element.blur(); element.dispatchEvent(new Event('blur')); } catch (error) { } if (type === 'checkbox' || type === 'radio') { break; } } } return result; }; return this.evaluate(callback as unknown as EvaluateFunc<[ElementHandle, T]>, data) as any; }; Super.prototype.fillFormByName = function >(data: T) { let callback = (node: HTMLFormElement, data: T, heuristic: 'css' | 'label' | 'name' | 'xpath' = 'css') => { if (node.nodeName.toLowerCase() !== 'form') { throw new Error('Element is not a element.'); } let result: Record = {}; for (let [key, value] of Object.entries(data)) { let selector = `[name="${key}"]`; if (result.hasOwnProperty(key) !== true) { result[key] = []; } let elements: HTMLInputElement[] = Array.from(node.querySelectorAll(selector)); if (elements.length === 0) { throw new Error(`No elements match the selector '${selector}' for '${key}'.`); } let type = (elements[0].getAttribute('type') || elements[0].nodeName).toLowerCase(); let values: (boolean | string)[] = (Array.isArray(value) === true) ? value as (boolean | string)[] : [value] as (boolean | string)[]; if (type === 'file') { throw new Error(`Input element of type 'file' is not supported.`); } for (let element of elements) { try { element.focus(); element.dispatchEvent(new Event('focus')); } catch (error) { } if (type === 'select') { element.value = undefined; for (let index of ['value', 'label'] as ['value', 'label']) { if (result[key].length > 0) { break; } for (let option of Array.from((element as unknown as HTMLSelectElement).options)) { option.selected = values.includes(option[index]); if (option.selected === true) { result[key].push(option.value); if (element.multiple !== true) { break; } } } } } else if (type === 'checkbox' || type === 'radio') { element.checked = (value === true) || values.includes(element.value); if (element.checked === true) { result[key].push(element.value); } } else if (typeof value === 'string') { if (element.isContentEditable === true) { result[key].push(element.textContent = value); } else { result[key].push(element.value = value); } } for (let trigger of ['input', 'change']) { element.dispatchEvent(new Event(trigger, { 'bubbles': true })); } try { element.blur(); element.dispatchEvent(new Event('blur')); } catch (error) { } if (type === 'checkbox' || type === 'radio') { break; } } } return result; }; return this.evaluate(callback as unknown as EvaluateFunc<[ElementHandle, T]>, data) as any; }; Super.prototype.fillFormBySelector = function >(data: T) { let callback = (node: HTMLFormElement, data: T, heuristic: 'css' | 'label' | 'name' | 'xpath' = 'css') => { if (node.nodeName.toLowerCase() !== 'form') { throw new Error('Element is not a element.'); } let result: Record = {}; for (let [key, value] of Object.entries(data)) { let selector = key; if (result.hasOwnProperty(key) !== true) { result[key] = []; } let elements: HTMLInputElement[] = Array.from(node.querySelectorAll(selector)); if (elements.length === 0) { throw new Error(`No elements match the selector '${selector}' for '${key}'.`); } let type = (elements[0].getAttribute('type') || elements[0].nodeName).toLowerCase(); let values: (boolean | string)[] = (Array.isArray(value) === true) ? value as (boolean | string)[] : [value] as (boolean | string)[]; if (type === 'file') { throw new Error(`Input element of type 'file' is not supported.`); } for (let element of elements) { try { element.focus(); element.dispatchEvent(new Event('focus')); } catch (error) { } if (type === 'select') { element.value = undefined; for (let index of ['value', 'label'] as ['value', 'label']) { if (result[key].length > 0) { break; } for (let option of Array.from((element as unknown as HTMLSelectElement).options)) { option.selected = values.includes(option[index]); if (option.selected === true) { result[key].push(option.value); if (element.multiple !== true) { break; } } } } } else if (type === 'checkbox' || type === 'radio') { element.checked = (value === true) || values.includes(element.value); if (element.checked === true) { result[key].push(element.value); } } else if (typeof value === 'string') { if (element.isContentEditable === true) { result[key].push(element.textContent = value); } else { result[key].push(element.value = value); } } for (let trigger of ['input', 'change']) { element.dispatchEvent(new Event(trigger, { 'bubbles': true })); } try { element.blur(); element.dispatchEvent(new Event('blur')); } catch (error) { } if (type === 'checkbox' || type === 'radio') { break; } } } return result; }; return this.evaluate(callback as unknown as EvaluateFunc<[ElementHandle, T]>, data) as any; }; Super.prototype.fillFormByXPath = function >(data: T) { let callback = (node: HTMLFormElement, data: T) => { if (node.nodeName.toLowerCase() !== 'form') { throw new Error('Element is not a element.'); } let result: Record = {}; for (let [key, value] of Object.entries(data)) { let selector = key; if (result.hasOwnProperty(key) !== true) { result[key] = []; } let element: Node = null; let elements: HTMLInputElement[] = []; let iterator = document.evaluate(selector, node, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null); while ((element = iterator.iterateNext()) != null) { elements.push(element as HTMLInputElement); } if (elements.length === 0) { throw new Error(`No elements match the selector '${selector}' for '${key}'.`); } let type = (elements[0].getAttribute('type') || elements[0].nodeName).toLowerCase(); let values: (boolean | string)[] = (Array.isArray(value) === true) ? value as (boolean | string)[] : [value] as (boolean | string)[]; if (type === 'file') { throw new Error(`Input element of type 'file' is not supported.`); } for (let element of elements) { try { element.focus(); element.dispatchEvent(new Event('focus')); } catch (error) { } if (type === 'select') { element.value = undefined; for (let index of ['value', 'label'] as ['value', 'label']) { if (result[key].length > 0) { break; } for (let option of Array.from((element as unknown as HTMLSelectElement).options)) { option.selected = values.includes(option[index]); if (option.selected === true) { result[key].push(option.value); if (element.multiple !== true) { break; } } } } } else if (type === 'checkbox' || type === 'radio') { element.checked = (value === true) || values.includes(element.value); if (element.checked === true) { result[key].push(element.value); } } else if (typeof value === 'string') { if (element.isContentEditable === true) { result[key].push(element.textContent = value); } else { result[key].push(element.value = value); } } for (let trigger of ['input', 'change']) { element.dispatchEvent(new Event(trigger, { 'bubbles': true })); } try { element.blur(); element.dispatchEvent(new Event('blur')); } catch (error) { } if (type === 'checkbox' || type === 'radio') { break; } } } return result; }; return this.evaluate(callback as unknown as EvaluateFunc<[ElementHandle, T]>, data) as any; }; Super.prototype.getInnerHTML = function () { return this.evaluate((node: Element) => { return (node as HTMLElement).innerHTML; }); }; Super.prototype.getInnerText = function () { return this.evaluate((node: Element) => { return (node as HTMLElement).innerText; }); }; Super.prototype.number = function (decimal: string = '.', property: any) { let callback = (node: any, decimal: string, property: any) => { let data = (node[property] as unknown) as string; if (typeof data === 'string') { decimal = decimal ?? '.'; if (typeof decimal === 'string') { decimal = decimal.replace(/[.]/g, '\\$&'); } let matches = data.match(/((?:[-+]|\b)[0-9]+(?:[ ,.'`ยด]*[0-9]+)*)\b/g); if (matches != null) { return matches.map((value) => parseFloat(value.replace(new RegExp(`[^-+0-9${decimal}]+`, 'g'), '').replace(decimal, '.'))); } } return null; }; return this.evaluate(callback, decimal, property as any); }; Super.prototype.selectByLabel = function (...values: string[]) { for (let value of values) { console.assert(typeof value === 'string', `Values must be strings. Found value '${value}' of type '${typeof value}'.`); } let callback = (node: HTMLSelectElement, values: string[]) => { if (node.nodeName.toLowerCase() !== 'select') { throw new Error('Element is not a