MULOC improves the robustness of test scripts by adding support for using multiple locators in E2E testing libraries through extended APIs.
MULOC currently supports Selenium WebDriver and WebdriverIO, and has been confirmed to work with Jest, ts-jest, and Mocha.
- Improving the robustness of test scripts by using multiple locators
- Automatic repair of broken locators
- Converting single-locator test scripts to multi-locator ones
- Automatic locator prioritization tailored to your project
See here for a complete usage example.
enableMultiLocator() extends selenium-webdriver as follows:
Importing recordFix is also required.
// part of a test script with selenium
const { Builder } = require("selenium-webdriver");
const { enableMultiLocator, recordFix } = require("multi-locator");
let driver = await new Builder().forBrowser("chrome").build();
driver = enableMultiLocator(driver);You can use findElementMulti() instead of findElement() in Selenium.
Arguments of findElementMulti() must be in hash format.
findElementMulti() locates web elements by trying locators in the order specified in .multi-locator/locator-order.config.
If a locator fails to locate a web element, the next locator is tried.
If the configuration file does not exist, the locators are tried from the beginning of the argument.
await driver.get("https://the-internet.herokuapp.com/login");
await driver
.findElementMulti(
{ id: "username" },
{ xpath: '//*[@id="username"]' },
{ css: "#username" },
{ name: "username" }
)
.sendKeys("tomsmith");If an application update has caused some locators to break, multiple locators can still be used to identify a web element as long as at least one of the locators is correct.
MULOC can be used to record the fixes for the broken locators by calling recordFix() once before finishing the tests.
When you use a testing framework, it is recommended to call it in a teardown method such as Jest's afterAll() or Mocha's after().
await driver
.findElementMulti(
{ id: "username_broken" }, // broken
{ xpath: '//*[@id="username_broken"]' }, // broken
{ css: "#username" },
{ name: "username_broken" } // broken
)
.sendKeys("tomsmith");
await recordFix(); //requiredRun npx muloc show fix <file name> after executing the test.
Then the test script with the locators fixed will be output to your console.
If the fix is OK, you can apply it to the file with npx muloc apply fix <file path>.
MULOC can convert test scripts using findElement to those using findElementMulti.
It adds as many of the available locators as possible to the argument.
await driver.findElement({ id: "password" }).sendKeys("SuperSecretPassword!");
await recordFix();After executing the test, running npx muloc show fix <file name> outputs:
// formatted
await driver
.findElementMulti(
{ id: "password" },
{ name: "password" },
{ xpath: "/html/body/div[2]/div/div/form/div[2]/div/input" }
)
.sendKeys("SuperSecretPassword!");
await recordFix();npx muloc apply order generates locator-order.config that prioritizes more robust locator types based on the number of times the locator types have been modified in the past.
Locator fix history is stored in .multi-locator/fix_history.json.
npx muloc show order shows the order of the locator types described in the config file.
npx muloc fix clear clears the fix history.
It is almost the same as for Selenium WebDriver, but there are some differences in coding rules.
First, you need to extend the browser or driver object.
browser = enableMultiLocator(browser);
await browser.url("https://the-internet.herokuapp.com/login");Unlike in Selenium, it is not possible to chain setValue(), click(), and other methods with the value returned by findElementMulti without resolving Promise when using WebdriverIO.
You must resolve the Promise before chaining the methods.
// cannot chain methods without await
await (
await browser.findElementMulti(
{ id: "username_broken" },
{ xpath: '//*[@id="username_broken"]' },
{ css: "#username" },
{ name: "username_broken" }
)
).setValue("tomsmith");
// not work
await browser
.findElementMulti(
{ id: "username_broken" },
{ xpath: '//*[@id="username_broken"]' },
{ css: "#username" },
{ name: "username_broken" }
)
.setValue("tomsmith");
// method chain using $
await $(
await browser.findElementMulti(
{ id: "username_broken" },
{ xpath: '//*[@id="username_broken"]' },
{ css: "#username" },
{ name: "username_broken" }
)
).$(<locator>).$(<locator>).setValue("tomsmith");enableMultiLocator(<driver>|<browser>)
returns an extended driver so that the following functions can be available.
recordFix()
Call this only once before finishing tests to record the modifications.
Repaired test scripts .multi-locator/fixed/<file name> and fix_history.json are generated.
findElement({<locator type>: <locator value>})
collects information to extend the locator in addition to the ordinary findElement behavior.
findElementMulti({<locator type>: <locator value>}, ...)
takes multiple locators as arguments and tries them in the order specified in the config file.
If there is no config, this tries the locators sequentially, starting with the first locator in the argument.
findElementMultiStrict({<locator type>: <locator value>}, ...)
ignores the configured locator order and tries the locators from the beginning of the arguments.
muloc show fix <file name>
outputs a modified test script given a file name (not a file path).
muloc apply fix <file path>
By giving a file path (not a file name), this applies the modification to the specified file.
muloc show order
outputs the content of .multi-locator/locator-order.config
muloc apply order
calculates the order of locators based on the content of fix_history.json and generates .multi-locator/locator-order.config.
muloc clear fix
clears fix_history.json.
The following locators can be used:
id, name, xpath, linkText, partialLinkText, innerText, partialInnerText, css
Please provide a locator as a string directly as an argument for findElement() or findElementMulti().
Using a variable that contains a string representing the locator will not work correctly.
// ok
await driver.findElement({ id: "password" }).sendKeys("SuperSecretPassword!");
// also ok
const element = driver.findElement({ id: "password" });
await element.sendKeys("SuperSecretPassword!");
// not work
const locatorValue = "password";
await driver
.findElement({ id: `${locatorValue}` })
.sendKeys("SuperSecretPassword!");
// not work
const locator = { id: "password" };
await driver.findElement(locator).sendKeys("SuperSecretPassword!");Currently, MULOC does not support specifying locators using methods like By.id or By.css.