UNPKG

@copperjs/copper

Version:
127 lines (106 loc) 4.67 kB
import { FastifyPluginCallback } from 'fastify'; import { ElementHandle, Page } from 'puppeteer-core'; import { WebdriverError } from '../../../common/errors'; const W3C_ELEMENT_ID = 'element-6066-11e4-a52e-4f735466cecf'; export const elementHandles = new (class { private pages = new WeakMap<Page, Map<string, ElementHandle>>(); private getPageMap(page: Page) { if (this.pages.has(page)) { return this.pages.get(page) as Map<string, ElementHandle>; } const elementsMap = new Map<string, ElementHandle>(); this.pages.set(page, elementsMap); return elementsMap; } getElementId(element: ElementHandle) { return element._remoteObject.objectId ?? ''; } set(page: Page, element: ElementHandle) { const elementsMap = this.getPageMap(page); const elementId = this.getElementId(element); if (elementsMap.has(elementId)) { return elementsMap.get(elementId); } else { elementsMap.set(elementId, element); } } get(page: Page, elementId: string) { const elementsMap = this.getPageMap(page); return elementsMap.get(elementId); } })(); export const elements: FastifyPluginCallback = (app, opts, done) => { app.post<{ Body: { using: 'css selector' | 'xpath'; value: string } }>('/element', async (req) => { // we intentionally ignore most locator strategies that are not CSS selectors or xpath // If someone is interested in implementing the other ones it should be easy and be my guest. const { using, value } = req.body; if (!using || !value) { throw new WebdriverError('missing either using or value'); } let elementHandle: ElementHandle<Element> | null = null; if (using === 'css selector') { elementHandle = await req.puppeteer.page.$(value); } if (using === 'xpath') { elementHandle = (await req.puppeteer.page.$x(value))[0]; } if (!elementHandle) { throw new WebdriverError('no such element'); } elementHandles.set(req.puppeteer.page, elementHandle); return { status: 0, value: { [W3C_ELEMENT_ID]: elementHandles.getElementId(elementHandle) }, state: 'success' }; }); app.get<{ Params: { elementId: string } }>('/element/:elementId/text', async (req) => { const { elementId } = req.params; if (!elementId) { throw new Error('missing element id'); } const elementHandle = elementHandles.get(req.puppeteer.page, elementId); if (!elementHandle) { throw new Error(`element ${elementId} does not exist`); } const value = await elementHandle.evaluate(/* istanbul ignore next */ (el) => el.innerHTML); return { status: 0, value, state: 'success' }; }); app.post<{ Params: { elementId: string } }>('/element/:elementId/click', async (req) => { const { elementId } = req.params; if (!elementId) { throw new Error('missing element id'); } const elementHandle = elementHandles.get(req.puppeteer.page, elementId); if (!elementHandle) { throw new Error(`element ${elementId} does not exist`); } await elementHandle.click(); return { status: 0, value: null, state: 'success' }; }); app.post<{ Params: { elementId: string }; Body: { value: string | string[] } }>( '/element/:elementId/value', async (req) => { const { elementId } = req.params; if (!elementId) { throw new Error('missing element id'); } const elementHandle = elementHandles.get(req.puppeteer.page, elementId); if (!elementHandle) { throw new Error(`element ${elementId} does not exist`); } const text = Array.isArray(req.body.value) ? req.body.value.join('') : req.body.value; await elementHandle.type(text); return { status: 0, value: null, state: 'success' }; }, ); app.post<{ Params: { elementId: string } }>('/element/:elementId/clear', async (req) => { const { elementId } = req.params; if (!elementId) { throw new Error('missing element id'); } const elementHandle = elementHandles.get(req.puppeteer.page, elementId); if (!elementHandle) { throw new Error(`element ${elementId} does not exist`); } await elementHandle.type(''); return { status: 0, value: null, state: 'success' }; }); done(); };