Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 68 additions & 82 deletions lib/core/engine/command/click.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { getLogger } from '@sitespeed.io/log';
import { executeCommand } from './commandHelper.js';
const log = getLogger('browsertime.command.click');

function addClick(js) {
Expand Down Expand Up @@ -45,14 +46,16 @@ export class Click {
* @throws {Error} Throws an error if the element is not found.
*/
async byClassName(className) {
try {
const script = `document.getElementsByClassName('${className}')[0].click();`;
await this.browser.runScript(script, 'CUSTOM');
} catch (error) {
log.error('Could not find element by class name %s', className);
log.verbose(error);
throw new Error('Could not find element by class name ' + className);
}
return executeCommand(
log,
'Could not find element by class name %s',
className,
() =>
this.browser.runScript(
`document.getElementsByClassName('${className}')[0].click();`,
'CUSTOM'
)
);
}

/**
Expand All @@ -76,14 +79,9 @@ export class Click {
* @throws {Error} Throws an error if the link is not found.
*/
async byLinkText(text) {
try {
const xpath = `//a[text()='${text}']`;
return this.byXpath(xpath);
} catch (error) {
log.error('Could not find link by text %s', text);
log.verbose(error);
throw new Error('Could not find link by text ' + text);
}
return executeCommand(log, 'Could not find link by text %s', text, () =>
this.byXpath(`//a[text()='${text}']`)
);
}

/**
Expand All @@ -95,14 +93,9 @@ export class Click {
* @throws {Error} Throws an error if the link is not found.
*/
async byLinkTextAndWait(text) {
try {
const xpath = `//a[text()='${text}']`;
return this.byXpathAndWait(xpath);
} catch (error) {
log.error('Could not find link with text %s', text);
log.verbose(error);
throw new Error('Could not find link by text ' + text);
}
return executeCommand(log, 'Could not find link by text %s', text, () =>
this.byXpathAndWait(`//a[text()='${text}']`)
);
}

/**
Expand All @@ -114,14 +107,12 @@ export class Click {
* @throws {Error} Throws an error if the link is not found.
*/
async byPartialLinkText(text) {
try {
const xpath = `//a[contains(text(),'${text}')]`;
return this.byXpath(xpath);
} catch (error) {
log.error('Could not find link by partial text %s', text);
log.verbose(error);
throw new Error('Could not find link by partial text ' + text);
}
return executeCommand(
log,
'Could not find link by partial text %s',
text,
() => this.byXpath(`//a[contains(text(),'${text}')]`)
);
}

/**
Expand All @@ -133,14 +124,12 @@ export class Click {
* @throws {Error} Throws an error if the link is not found.
*/
async byPartialLinkTextAndWait(text) {
try {
const xpath = `//a[contains(text(),'${text}')]`;
return this.byXpathAndWait(xpath);
} catch (error) {
log.error('Could not find link by partial text %s', text);
log.verbose(error);
throw new Error('Could not find link by partial text ' + text);
}
return executeCommand(
log,
'Could not find link by partial text %s',
text,
() => this.byXpathAndWait(`//a[contains(text(),'${text}')]`)
);
}

/**
Expand All @@ -152,16 +141,17 @@ export class Click {
* @throws {Error} Throws an error if the element is not found.
*/
async byXpath(xpath) {
try {
// This is how Selenium do internally
const replaced = xpath.replaceAll('"', "'");
const script = `document.evaluate("${replaced}", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue.click();`;
return this.browser.runScript(script, 'CUSTOM');
} catch (error) {
log.error('Could not find element by xpath %s', xpath);
log.verbose(error);
throw new Error('Could not find element by xpath ' + xpath);
}
return executeCommand(
log,
'Could not find element by xpath %s',
xpath,
() => {
// This is how Selenium do internally
const replaced = xpath.replaceAll('"', "'");
const script = `document.evaluate("${replaced}", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue.click();`;
return this.browser.runScript(script, 'CUSTOM');
}
);
}
/**
* Clicks on an element that matches a given XPath selector and waits for the page complete check to finish.
Expand All @@ -184,14 +174,12 @@ export class Click {
* @throws {Error} Throws an error if the element is not found.
*/
async byJs(js) {
try {
const script = addClick(js);
await this.browser.runScript(script, 'CUSTOM');
} catch (error) {
log.error('Could not find element by JavaScript %s', js);
log.verbose(error);
throw new Error('Could not find element by JavaScript ' + js);
}
return executeCommand(
log,
'Could not find element by JavaScript %s',
js,
() => this.browser.runScript(addClick(js), 'CUSTOM')
);
}

/**
Expand All @@ -215,14 +203,12 @@ export class Click {
* @throws {Error} Throws an error if the element is not found.
*/
async byId(id) {
try {
const script = `document.getElementById('${id}').click();`;
await this.browser.runScript(script, 'CUSTOM');
} catch (error) {
log.error('Could not find element by id %s', id);
log.verbose(error);
throw new Error('Could not find element by id ' + id);
}
return executeCommand(log, 'Could not find element by id %s', id, () =>
this.browser.runScript(
`document.getElementById('${id}').click();`,
'CUSTOM'
)
);
}

/**
Expand All @@ -234,14 +220,12 @@ export class Click {
* @throws {Error} Throws an error if the element is not found.
*/
async byName(name) {
try {
const script = `document.querySelector("[name='${name}']").click()`;
await this.browser.runScript(script, 'CUSTOM');
} catch (error) {
log.error('Could not find element by name %s', name);
log.verbose(error);
throw new Error('Could not find element by name ' + name);
}
return executeCommand(log, 'Could not find element by name %s', name, () =>
this.browser.runScript(
`document.querySelector("[name='${name}']").click()`,
'CUSTOM'
)
);
}

/**
Expand All @@ -263,14 +247,16 @@ export class Click {
* @throws {Error} Throws an error if the element is not found.
*/
async bySelector(selector) {
try {
const script = `document.querySelector('${selector}').click();`;
await this.browser.runScript(script, 'CUSTOM');
} catch (error) {
log.error('Could not click using selector %s', selector);
log.verbose(error);
throw new Error('Could not click using selector ' + selector);
}
return executeCommand(
log,
'Could not click using selector %s',
selector,
() =>
this.browser.runScript(
`document.querySelector('${selector}').click();`,
'CUSTOM'
)
);
}

/**
Expand Down
30 changes: 30 additions & 0 deletions lib/core/engine/command/commandHelper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* Shared error handling wrapper for command methods.
*
* Most command methods follow the same try/catch pattern:
* log.error + log.verbose + throw new Error. This helper
* eliminates that boilerplate.
*
* @param {Object} log - The logger instance.
* @param {string} errorMessage - printf-style message (with %s placeholder).
* @param {string|undefined} identifier - Value to interpolate into the message, or undefined if none.
* @param {Function} fn - The async function to execute.
* @returns {Promise<*>} The return value of fn.
*/
export async function executeCommand(log, errorMessage, identifier, fn) {
try {
return await fn();
} catch (error) {
if (identifier === undefined) {
log.error(errorMessage);
} else {
log.error(errorMessage, identifier);
}
log.verbose(error);
throw new Error(
identifier === undefined
? errorMessage
: errorMessage.replace('%s', String(identifier))
);
}
}
12 changes: 4 additions & 8 deletions lib/core/engine/command/javaScript.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { getLogger } from '@sitespeed.io/log';
import { executeCommand } from './commandHelper.js';
const log = getLogger('browsertime.command.javascript');
/**
* Provides functionality to execute JavaScript code in the context of the current page.
Expand Down Expand Up @@ -36,14 +37,9 @@ export class JavaScript {
* @throws {Error} Throws an error if the JavaScript cannot be executed.
*/
async run(js) {
try {
const value = await this.browser.runScript(js, 'CUSTOM');
return value;
} catch (error) {
log.error('Could not run JavaScript %s ', js);
log.verbose(error);
throw new Error(`Could not run JavaScript ${js}`);
}
return executeCommand(log, 'Could not run JavaScript %s', js, () =>
this.browser.runScript(js, 'CUSTOM')
);
}

/**
Expand Down
Loading
Loading