Skip to content

Commit

Permalink
Documentation update (#2062)
Browse files Browse the repository at this point in the history
  • Loading branch information
soulgalore authored Jan 7, 2024
1 parent f927247 commit 88eb3b3
Show file tree
Hide file tree
Showing 15 changed files with 265 additions and 48 deletions.
4 changes: 4 additions & 0 deletions jsdoc/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# Scripting

<img src="https://www.sitespeed.io/img/user-journey.png" style="float: right !important;" alt="The user journey" width="250">

Welcome to the powerful world of scripting with sitespeed.io! This feature unlocks the potential to simulate real user journeys, measure performance, and gather detailed metrics by interacting with web pages through custom scripts. Whether you're looking to analyze simple page loads or complex user interactions, our scripting functionality offers the tools you need.


## Key Features

* **User Journey Simulation**: Script entire user flows, from navigation to clicks and form submissions, to capture a realistic user experience.
Expand Down
4 changes: 2 additions & 2 deletions jsdoc/jsdoc.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
},
"opts": {
"tutorials": "./jsdoc/tutorials/",
"destination": "../sitespeed.io/docs/documentation/sitespeed.io/scripting/tutorials",
"destination": "../sitespeed.io/docs/documentation/sitespeed.io/scripting/",
"template": "node_modules/clean-jsdoc-theme",
"theme_opts": {
"homepageTitle": "Tutorials and documentation for scripting in Browsertime and sitespeed.io",
Expand All @@ -33,7 +33,7 @@
"link": "."
},
{
"title": "Documentation",
"title": "Back to documentation",
"link": "https://www.sitespeed.io/documentation/sitespeed.io/"
}
],
Expand Down
39 changes: 38 additions & 1 deletion jsdoc/tutorials/04-Interact-with-the-page.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,46 @@ One of the key things in your script is to be able to find the right element to
### Using Chrome to find the CSS Selector to the element
![Using Chrome to find the selector](https://www.sitespeed.io/img/selector-chrome.png)

## Using Actions
Since Browsertime 21.0.0 we support easier access to the [Selenium Action API](https://www.selenium.dev/documentation/webdriver/actions_api/). That makes easier to interact with the page and you can also chain commands. You can checkout the [Selenium NodeJS Action API](https://www.selenium.dev/selenium/docs/api/javascript/module/selenium-webdriver/lib/input_exports_Actions.html) to see more what you can do.

Here's an example doing search on Wikipedia:
```javascript
/**
* @param {import('browsertime').BrowsertimeContext} context
* @param {import('browsertime').BrowsertimeCommands} commands
*/
export default async function (context, commands) {
await commands.measure.start('https://www.wikipedia.org');
const searchBox = await commands.element.getById('searchInput');
const submitButton = await commands.element.getByClassName(
'pure-button pure-button-primary-progressive'
);

await commands.measure.start('Search');
await commands.action
.getActions()
.move({ origin: searchBox })
.pause(1000)
.press()
.sendKeys('Hepp')
.pause(200)
.click(submitButton)
.perform();

// If you would do more actions after calling .perform()
// you manually need to clear the action API
//await commands.action.clear();

await commands.wait.byPageToComplete();
return commands.measure.stop();
}
```


## JavaScript

You can run your own JavaScript in the browser from your script. This is powerful becasue that makes it possible to do whatever you want :)
You can run your own JavaScript in the browser from your script. This is powerful because that makes it possible to do whatever you want :)

### Run
Run JavaScript. Will throw an error if the JavaScript fails.
Expand Down
75 changes: 34 additions & 41 deletions jsdoc/tutorials/09-Examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,40 @@ export default async function (context, commands) {
};
```

### Measuring Interaction to next paint - INP
One of the new metrics Google is pushing is [Interaction to next paint](https://web.dev/articles/inp). You can use it when you collect RUM and using sitespeed.io. To measure it you need to interact with a web page. The best way to do that is using the Action API.


```JavaScript
/**
* @param {import('browsertime').BrowsertimeContext} context
* @param {import('browsertime').BrowsertimeCommands} commands
*/
export default async function (context, commands) {
// Start to measure
await commands.measure.start();
// Go to a page ...
await commands.navigate('https://en.m.wikipedia.org/wiki/Barack_Obama');

// When the page has finished loading you can find the navigation and click on it
const element = await commands.element.getByXpath(
'//*[@id="mw-mf-main-menu-button"]'
);
await commands.action.getActions().click(element).perform();

// If you want to do multiple actions, remember to clear() the Action API manually

// Add some wait for the menu to show up
await commands.wait.byTime(2000);

// Measure everything, that means you will run the JavaScript that collects the interaction to next paint
return commands.measure.stop();
}
```

You will see the metric in the page summary and in the metrics section.


### Measure a login step

```JavaScript
Expand Down Expand Up @@ -328,47 +362,6 @@ export default async function (context, commands) {
};
```


### Measuring Interaction to next paint - INP
One of the new metrics Google is pushing is [First Input Delay](https://developers.google.com/web/updates/2018/05/first-input-delay). You can use it when you collect RUM but it can be hard to know what the user is doing. The recommended way is to use the Long Task API but the truth is that the attribution from the API is ... well can be better. When you have a long task, it is really hard to know why by looking at the attribution.

How do we measure FID with sitespeed.io? You can measure clicks and button using the [Selenium Action API](https://selenium.dev/selenium/docs/api/javascript/module/selenium-webdriver/lib/input_exports_Actions.html) and then sitespeed.io uses the `first-input` performance observer to get it. What's really cool is that you can really measure it, instead of doing guestimates.

Here's an example on measuring open the navigation on Wikipedia on mobile. I run my tests on a Alacatel One phone.

```JavaScript
/**
* @param {import('browsertime').BrowsertimeContext} context
* @param {import('browsertime').BrowsertimeCommands} commands
*/
export default async function (context, commands) {
// We have some Selenium context
const webdriver = context.selenium.webdriver;
const driver = context.selenium.driver;

// Start to measure
await commands.measure.start();
// Go to a page ...
await commands.navigate('https://en.m.wikipedia.org/wiki/Barack_Obama');

// When the page has finished loading you can find the navigation and click on it
const actions = driver.actions();
const nav = await driver.findElement(
webdriver.By.xpath('//*[@id="mw-mf-main-menu-button"]')
);
await actions.click(nav).perform();

// Measure everything, that means you will run the JavaScript that collects the first input delay
return commands.measure.stop();
};
```

You will see the metric in the page summary and in the metrics section.

![First input delay](https://www.sitespeed.io/img/first-input-delay.png)

You can do mouse click, key press but there's no good way to do swiping as we know using the [Selenium Action API](https://selenium.dev/selenium/docs/api/javascript/module/selenium-webdriver/lib/input_exports_Actions.html). Your action will run after the page has loaded. If you wanna know what kind potential input delay you can have on load, you can use the *maxPotentialFid* metric that you will get by enabling `--cpu`.

### Test multiple URLs

If you want to test multiple URLs and need to do some specific things before each URL, you can do something like this (we pass on our [own options](#pass-your-own-options-to-your-script) to the script):
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,12 @@
"devDependencies": {
"@types/selenium-webdriver": "4.1.21",
"ava": "6.0.1",
"clean-jsdoc-theme": "^4.2.17",
"clean-jsdoc-theme": "4.2.17",
"eslint": "8.55.0",
"eslint-config-prettier": "9.1.0",
"eslint-plugin-prettier": "5.0.1",
"eslint-plugin-unicorn": "49.0.0",
"jsdoc": "^4.0.2",
"jsdoc": "4.0.2",
"prettier": "3.1.1",
"serve": "14.2.1",
"serve-handler": "6.1.5",
Expand Down
73 changes: 73 additions & 0 deletions types/core/engine/command/action.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/**
* This class provides an abstraction layer for Selenium's action sequence functionality.
* It allows for easy interaction with web elements using different locating strategies
* and simulating complex user gestures like mouse movements, key presses, etc.
*
* @class
* @hideconstructor
* @see https://www.selenium.dev/documentation/webdriver/actions_api/
*/
export class Action {
constructor(browser: any);
/**
* @private
*/
private driver;
/**
* @private
*/
private actions;
clear(): Promise<any>;
/**
* Retrieves the current action sequence builder.
* The actions builder can be used to chain multiple browser actions.
* @returns {SeleniumActions} The current Selenium Actions builder object for chaining browser actions.
* @example
* // Example of using the actions builder to perform a drag-and-drop operation:
* const elementToDrag = await commands.action.getElementByCss('.draggable');
* const dropTarget = await commands.action.getElementByCss('.drop-target');
* await commands.action.getAction()
* .dragAndDrop(elementToDrag, dropTarget)
* .perform();
*
*/
getAction(): SeleniumActions;
/**
* Finds an element by its CSS selector.
*
* @param {string} name - The CSS selector of the element.
* @returns {Promise<WebElement>} A promise that resolves to the WebElement found.
*/
getElementByCss(name: string): Promise<WebElement>;
/**
* Finds an element by its ID.
*
* @param {string} id - The ID of the element.
* @returns {Promise<WebElement>} A promise that resolves to the WebElement found.
*/
getElementById(id: string): Promise<WebElement>;
/**
* Finds an element by its XPath.
*
* @param {string} xpath - The XPath query of the element.
* @returns {Promise<WebElement>} A promise that resolves to the WebElement found.
*/
getElementByXpath(xpath: string): Promise<WebElement>;
/**
* Finds an element by its class name.
*
* @param {string} className - The class name of the element.
* @returns {Promise<WebElement>} A promise that resolves to the WebElement found.
*/
getElementByClassName(className: string): Promise<WebElement>;
/**
* Finds an element by its name attribute.
*
* @param {string} name - The name attribute of the element.
* @returns {Promise<WebElement>} A promise that resolves to the WebElement found.
*/
getElementByName(name: string): Promise<WebElement>;
}
import { Actions as SeleniumActions } from 'selenium-webdriver';
import { WebElement } from 'selenium-webdriver';
//# sourceMappingURL=action.d.ts.map
1 change: 1 addition & 0 deletions types/core/engine/command/action.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

38 changes: 38 additions & 0 deletions types/core/engine/command/actions.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* This class provides an abstraction layer for Selenium's action sequence functionality.
* It allows for easy interaction with web elements using different locating strategies
* and simulating complex user gestures like mouse movements, key presses, etc.
*
* @class
* @hideconstructor
* @see https://www.selenium.dev/documentation/webdriver/actions_api/
* @see https://www.selenium.dev/selenium/docs/api/javascript/module/selenium-webdriver/lib/input_exports_Actions.html
*/
export class Actions {
constructor(browser: any);
/**
* @private
*/
private driver;
/**
* @private
*/
private actions;
clear(): Promise<any>;
/**
* Retrieves the current action sequence builder.
* The actions builder can be used to chain multiple browser actions.
* @returns {SeleniumActions} The current Selenium Actions builder object for chaining browser actions.
* @example
* // Example of using the actions builder to perform a drag-and-drop operation:
* const elementToDrag = await commands.action.getElementByCss('.draggable');
* const dropTarget = await commands.action.getElementByCss('.drop-target');
* await commands.action.getAction()
* .dragAndDrop(elementToDrag, dropTarget)
* .perform();
*
*/
getActions(): SeleniumActions;
}
import { Actions as SeleniumActions } from 'selenium-webdriver/lib/input.js';
//# sourceMappingURL=actions.d.ts.map
1 change: 1 addition & 0 deletions types/core/engine/command/actions.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

49 changes: 49 additions & 0 deletions types/core/engine/command/element.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/**
* This class provides a way to get hokld of Seleniums WebElements.
* @class
* @hideconstructor
*/
export class Element {
constructor(browser: any);
/**
* @private
*/
private driver;
/**
* Finds an element by its CSS selector.
*
* @param {string} name - The CSS selector of the element.
* @returns {Promise<WebElement>} A promise that resolves to the WebElement found.
*/
getByCss(name: string): Promise<WebElement>;
/**
* Finds an element by its ID.
*
* @param {string} id - The ID of the element.
* @returns {Promise<WebElement>} A promise that resolves to the WebElement found.
*/
getById(id: string): Promise<WebElement>;
/**
* Finds an element by its XPath.
*
* @param {string} xpath - The XPath query of the element.
* @returns {Promise<WebElement>} A promise that resolves to the WebElement found.
*/
getByXpath(xpath: string): Promise<WebElement>;
/**
* Finds an element by its class name.
*
* @param {string} className - The class name of the element.
* @returns {Promise<WebElement>} A promise that resolves to the WebElement found.
*/
getByClassName(className: string): Promise<WebElement>;
/**
* Finds an element by its name attribute.
*
* @param {string} name - The name attribute of the element.
* @returns {Promise<WebElement>} A promise that resolves to the WebElement found.
*/
getByName(name: string): Promise<WebElement>;
}
import { WebElement } from 'selenium-webdriver';
//# sourceMappingURL=element.d.ts.map
1 change: 1 addition & 0 deletions types/core/engine/command/element.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions types/core/engine/command/measure.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,13 @@ export class Measure {
* If an alias is provided, or no URL is available, it sets up the environment for a user-driven navigation.
*
* @async
* @example
* await commands.measure.start('https://www.example.org');
* // Or start the measurement and click on a link
* await commands.measure.start();
* await commands.click.byLinkTextAndWait('Documentation');
* // Remember to stop the measurements if you do not provide a URL
* await commands.measure.stop();
* @param {string} urlOrAlias - The URL to navigate to, or an alias representing the test.
* @param {string} [optionalAlias] - An optional alias that can be used if the first parameter is a URL.
* @throws {Error} Throws an error if navigation fails or if there are issues in the setup process.
Expand Down
2 changes: 1 addition & 1 deletion types/core/engine/command/measure.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 88eb3b3

Please sign in to comment.