Common Luke Functions
Luke is an all-purpose UI testing framework for web applications. This document provides descriptions and examples for commonly used functions.
LukeBrowser functions
These functions are called on an instance of LukeBrowser.
In the Luke Playground, the following variables have been initialized by an instance of LukeBrowser: client, luke, this.client, this.luke. These variables can be directly used to execute LukeBrowser functions such as:
luke.search('.c3-button-primary');In a Luke test file, you must initialize an instance of LukeBrowser to run these functions:
this.client = LukeBrowser.init();
this.client.search('.c3-button-primary');Goto - goto(location)
Navigates to a specific location/url.
this.client.goto('/index_lukeDemoPage.html');This code will navigate to https://host-url/index_lukeDemoPage.html
Search - search(selector, resolveOnFound, timeout)
Searches for an element by the css selector.
this.client.search('input.c3-form-field:nth-of-type(1)', true, 20);This code returns the first element with the selector input.c3-form-field:nth-of-type(1). It will fail if the element is not found after 20 seconds.
this.client.search('input.c3-form-field:nth-of-type(1)', false);This code returns the first element with the selector input.c3-form-field:nth-of-type(1). It will return even if the element is not found.
SearchAll - searchAll(selector, resolveOnFound, timeout)
Searches for and returns a LukeAsyncQueueCollection of all the elements that match the css selector.
this.client.searchAll('input.c3-form-field', true, 20);This code returns the elements that match the selector input.c3-form-field. It will fail if elements are not found after 20 seconds.
this.client.searchAll('input.c3-form-field', false);This code returns the elements that match the selector input.c3-form-field. It will return even if elements are not found.
Click - click(selector, timeout)
Clicks an element by a css selector.
this.client.click('.c3-button-primary', 20);This code clicks on the element that matches the selector .c3-button-primary. It will fail if it cannot click the element within 20 seconds.
SetValue - setValue(selector, value, timeout)
Sets the specified value on an element that matches a css selector.
this.client.setValue('input.c3-form-field:nth-of-type(1)', 'test-user');GetValue - getValue(selector, timeout)
Gets value from an element that matches a css selector.
this.client.getValue('input.c3-form-field:nth-of-type(1)');AddRequestMock - addRequestMock(type, action, response, status, headers, timeout)
Adds a request mock with a static response for a pair of type and action.
this.client.addRequestMock('User', 'fetch', {
objs: [
{
id: 'test',
name: 'test',
},
],
});This code adds a mock for the call User.fetch(). This means, whenever that call is made, the response will always be the object specified above with ID test and name test.
AddRequestMockWithHandler - addRequestMockWithHandler(type, action, handler, args, timeout)
Adds a request mock with a handler function for a pair of type and action.
this.client.addRequestMockWithHandler('User', 'fetch', function (params, respond, targetId) {
var filter = params.spec && params.spec.filter;
// targetId is passed in as an additional argument
if (filter && filter.indexOf(targetId) > -1) {
respond({
count: 1,
objs: [
{
id: 'test-user-id',
name: 'test',
},
],
});
} else {
respond({ message: 'unauthorized' }, 401, { 'X-Frame-Options': 'SAMEORIGIN' });
}
}, ['test-user-id']);This code adds a mock for the call User.fetch() with a handler. This means, whenever that call is made, the response will be one of the two specified above depending on the parameters of the call.
ClearRequestMocks - clearRequestMocks(timeout)
Clears all existing request mocks.
this.client.clearRequestMocks()Then - then(callback, args, nodeType, label)
Adds a LukeAsyncQueueNode to your Luke Chain that can run additional logic beyond the default Luke functions. Note that the passed-in callback function must have the first two arguments reserved for the LukeBrowser instance as well as the value from the previous LukeAsyncQueueNode.
this.client.search('.selector-with-hello-world', true, 20).text().then(function (client, value, appendedString) {
return value + appendedString;
}, ['appended']).assert('toEqual', 'hello worldappended');CatchThen - catchThen(callback, args, nodeType, label)
Adds a LukeAsyncQueueNode to your Luke Chain that can run additional logic beyond the default Luke functions if a previous LukeAsyncQueueNode has failed. Otherwise, this node and following nodes are skipped. Note that the passed-in callback function must have the first two arguments reserved for the LukeBrowser instance as well as the value from the previous LukeAsyncQueueNode.
This is commmonly used to test "negative path" scenarios, where the test validates that something does not exist.
this.client.search('.non-existent-selector', true, 20).catchThen(function (client, value) {
client.updateDynamicValue('noSelector', true);
});
this.client.enqueue(function (client, value) {
// we expect the catchThen function to have triggered a change
expect(client.resolveDynamicValue('noSelector')).toBe(true);
})CompareScreenshot - compareScreenshot(relativeScreenshotPath, threshold, timeout, removeScreenshotsOnPass)
This function creates a comparison between the current active UI and some expected screenshot in the relativeScreenshotPath, relative to an "expected" directory nested in a package's "test" directory. The function does a pixel-based comparison and considers a pixel-difference percentage threshold, which must be passed in the arguments. The comparison is retried until the timeout limit is reached, and will generate screenshots of the actual, expected, and diff in the LukeCoreConfig#saveScreenShot path if the comparison fails.
If a user is synced to an environment with the C3 AI VS Code extension, the screenshots will also be generated in the synced package's test/screenshots/ directory.
If removeScreenshotsOnPass is "true", the screenshots will be removed from LukeCoreConfig#saveScreenShot and will not be generated in the synced package.
The function returns a LukeAsyncQueueNode that resolves to either "true" or "false", depending on the result of the comparison.
Note that when syncing, the source of truth configuration matters! If screenshots were generated while the environment was not synced to, the source of truth should be "Client" and the "Union" toggle should be enabled in the VS Code Extension. This way, any screenshots that were generated while not synced will be shown in the user's VS Code after syncing is complete.
it('compareScreenshot', function() {
// In this example, I have `home_page.png` in <package>/test/screenshots/expected/home_page.png, and I
// want the active test UI to only have a 10% pixel difference.
this.client.compareScreenshot('home_page.png', .1, 5, false).assert('toEqual', true);
});CompareElementScreenshot - compareElementScreenshot(selector, relativeScreenshotPath, threshold, timeout, removeScreenshotsOnPass)
This function is largely the same as compareScreenshot, but also considers a specific selector to give more granularity to the screenshot comparison. This is useful if you care about a specific component in the UI changing.
it('compareElementScreenshot', function() {
// In this example, I have `home_page_sidebar.png` in <package>/test/screenshots/expected/home_page_sidebar.png,
// and I want the active test UI component to only have a 10% pixel difference.
this.client.compareElementScreenshot('.sidebar', 'home_page_sidebar.png', .1, 5, false).assert('toEqual', true);
});Using SearchAll and LukeAsyncQueueCollection Functions
The searchAll function searches for and returns a LukeAsyncQueueCollection of all elements that match a specific selector. Once all elements are returned, there are many convenient functions that can be used on the LukeAsyncQueueCollection.
Each - each(funk, [args])
Iterates over each element within the collection and invokes the callback function funk for each element.
this.client.searchAll('button').each(function (client, button) {
return button.click();
});This code clicks on each element that is found for the selector button.
Filter - filter(funk, [args])
Filters a collection of elements.
this.client.searchAll('div.section').filter(function (client, section) {
return section.search('input').getValue().then(function (client, value) {
return parseInt(value) > 1;
});
});This code returns the filtered set of elements found with the selector div.section whose value within element input is > 1.
Find - find(funk, [args], resolveOnFound)
Finds an element that satisfies the predicate function funk.
this.client.searchAll('div.section').find(function (client, section) {
return section.search('input').getValue().then(function (client, value) {
return value === '2';
});
});Map - map(funk, [args])
Maps a collection of elements to a collection of the same element type after applying the function funk.
this.client.searchAll('input').map(function (client, input) {
return input.parentElement().search('button');
});This code returns a collection of buttons that are found within the parent element of any element with the selector input.
MapToAny - mapToAny(funk, [args])
Maps a collection of elements to a collection of LukeAsyncQueueNodes by wrapping any value.
this.client.searchAll('button').mapToAny(function (client, button) {
return button.text();
});This code returns a collection with the text of any element that has the selector button.
Reduce - reduce(funk, accumulator, [args])
Iterates through all the elements and aggregates their results to a single value using the predicate function funk.
this.client.searchAll('input').reduce(function (client, input, result, index) {
return input.getValue().then(function (client, value, _result) {
return _result + parseInt(value);
}, [result]);
}, 0);This code adds up the values of the elements found with selector input.
AtIndex - atIndex(index)
Returns the element at the given index.
this.client.searchAll('span').atIndex(0);Reverse - reverse()
Reverses the order of the elements in the collection.
this.client.searchAll('button').reverse();This code will return the buttons in the reverse order that they are found.
Size - size()
Returns the size of the collection of elements found
this.client.searchAll('span').size();LukeDynamicValue
Retrieving and storing values in Luke chains can be a pain due to asynchronous processing, leading to a long sequence of Luke chains. LukeDynamicValue solves this problem by allowing users to store values of nodes into Luke-managed values. This creates more readable code and flattens out the logic.
SaveAsDynamicValue - saveAsDynamicValue(name)
Saves the specified value as a LukeDynamicValue.
this.client.searchAll('.row').size().saveAsDynamicValue('originalValue');The code will store the output of this.client.searchAll('.row').size() as a LukeDynamicValue named originalValue. It essentially mimics var originalValue = ...;
UpdateDynamicValue - updateDynamicValue(name, value)
Updates the passed in dynamic value (either a LukeDynamicValue or a string) to a new value.
this.client.updateDynamicValue('existing-value', 'new value');ResolveDynamicValue - resolveDynamicValue(name)
Returns the value of the passed in dynamic value (either a LukeDynamicValue or a string).
this.client.enqueue(function (client) {
expect(client.resolveDynamicValue('existing-value')).toBe('new value');
})MakeValue - makeValue(name)
Makes an instance of LukeDynamicValue of the given name.
LukeDynamicValue.makeValue('originalValue');The code will output a LukeDynamicValue that retains the value that it was saved as. A more detailed but simple use case here:
/**
* We need to make sure the input value at our current page
* is the same as our next page. Saving values dynamically
* would be helpful here.
*/
describe('maintain same input', function () {
beforeAll(function () {
// saveAsDynamicValue mimics `var originalValue = ...;`
this.client.search('.input').getValue().saveAsDynamicValue('originalValue');
// Go to another page
this.client.search('.button-next').click();
});
it('the inputs should be the same', function () {
this.client.search('.input').getValue().assert('toEqual', LukeDynamicValue.makeValue('originalValue'));
});
});LukeBrowserWebElement functions
These functions are called on an instance of the LukeBrowserWebElement. For example:
this.client.search('input.c3-form-field:nth-of-type(1)').setValue('test-user');In this code, the element that matches the selector input.c3-form-field:nth-of-type(1) is returned, and then its value is set to be test-user.
Search - search(selector, resolveOnFound)
Searches for an element within the current element that matches the css selector.
this.client.search('.c3-form-group:nth-of-type(2)').search('.c3-form-field');This code will search for the css selector .c3-form-field within the element with the css selector .c3-form-group:nth-of-type(2).
SearchAll - searchAll(selector, resolveOnFound)
Searches for all elements within the current element that match the css selector.
this.client.search('.form-body').searchAll('.c3-form-group');This code will return all elements that match the selector .c3-form-group within the element with the css selector .form-body.
SearchForElementWithText - searchForElementWithText(selector, text, resolveOnFound)
Searches for all elements within the current element that match the css selector and returns the first element that has the given text.
this.client.search('.autocomplete-items').searchForElementWithText('div', 'United States of America');This code will return the element within autocomplete-items that matches the selector div and has the text United State of America.
Click - click()
Clicks the underlying element.
this.client.search('.c3-button-primary').click();This searches for the element with selector .c3-button-primary, then clicks on it.
MoveTo - moveTo(xOffset, yOffset)
Moves the mouse to the underlying element.
this.client.search('.c3-button-primary').moveTo();This function is particularly useful when a button only appears on hover. In that case, you could move to the selector that will reveal the button on hover.
SetValue - setValue(value)
Sets the value on the underlying element.
this.client.search('input.c3-form-field:nth-of-type(1)').setValue('test-user');This code will set the value for the element to test-user.
GetValue - getValue()
Gets the value from the underlying element.
this.client.search('input.c3-form-field:nth-of-type(1)').getValue();This code will get the value for the element specified by the css selector input.c3-form-field:nth-of-type(1).
Text - text()
Gets the text of the underlying element.
this.client.search('.c3-button-primary').text().assert('toEqual', 'Submit');This code will get the text of the button and make an assertion based on the returned value. (Assert the text of the button to equal Submit.)
Visible - visible(isInViewPort)
Gets the visibility of the underlying element.
this.client.search('.c3-form', true, 20).visible().assert('toEqual', true);This code makes an assertion that the form specified by selector .c3-form is visible.
Enabled - enabled()
Checks whether the underlying element is enabled.
this.client.search('.c3-button-primary').enabled().assert('toEqual', true);This code makes an assertion that the button specified by selector .c3-button-primary is enabled.
Selected - selected()
Checks whether the underlying element (an option element, an input element of type checkbox, or a radio button) is selected.
this.client.search('section.checkbox input').selected();ClassNames - classNames()
Gets the classNames of the underlying element.
this.client.search('input').classNames();This code will resolve to the classnames of the element with selector input.
InnerHTML - innerHTML()
Gets a string representation of the HTML nested under the corresponding element.
this.client.search('.form-body').innerHTML().assert('toContain', 'c3-form-group');This code will get the string representation of the html under the form-body element and assert that it contains the string c3-form-group.
InnerText - innerText()
Gets the inner text of the corresponding element.
this.client.search('.form-body').innerText().assert('toContain', 'Name');This code will get the inner text of the element for the selector .form-body and assert that it contains the string Name.
CssProperty - cssProperty(name)
Gets a css property from the element
this.client.search('.form-body').cssProperty('padding-top');This code will get the css property padding-top for the element with selector form-body.
Sample script
To run this script, follow the set-up in Luke Playground Tutorial - "Running Code in Fiddle Mode."
// Navigate to the correct url
this.client.goto('/luke/index_lukeDemoPage.html');
// Ensure the form is visible
this.client.search('.c3-form', true, 20).visible().assert('toEqual', true);
// Check that the form has the correct number of fields
this.client.searchAll('input.c3-form-field', true, 20).size().assert('toEqual', 4);
// Set the value for the first field
this.client.setValue('input.c3-form-field:nth-of-type(1)', 'test-user');
// Check that the value of the first field is correct
this.client.getValue('input.c3-form-field:nth-of-type(1)').assert('toEqual', 'test-user');
// Check that the second field is for the Email
this.client.search('.form-body').searchAll('.c3-form-group').atIndex(1).text().assert('toEqual', 'Email');
// Set the value for the second field of the form
this.client.search('.c3-form-group:nth-of-type(2) .c3-form-field').setValue('testuser@email.com');
// Check that the form group with the input 'test-user' has the label 'Name'
this.client.searchAll('.c3-form-group').find(function (client, group) {
return group.search('input').getValue().then(function (client, value) {
return value === 'test-user';
});
}).search('label').text().assert('toEqual', 'Name');
// Check the labels of the form inputs in reverse order
this.client.searchAll('.c3-form-group label')
.reverse()
.mapToAny(function (client, label) {
return label.text();
}).assert('toEqual', ['Country', 'Password', 'Email', 'Name']);
// Check that the form submit button is enabled
this.client.search('.c3-button-primary').enabled().assert('toEqual', true);
// Submit the form
this.client.search('.c3-button-primary').click();
// Check that the modal is present
this.client.search('.c3-modal').visible().assert('toEqual', true);
// Check that the modal displays an error
this.client.search('.c3-modal-footer').innerText().assert('toContain', 'error');
// Check that the modal contains className 'error'
this.client.search('.c3-modal-footer').classNames().assert('toContain', 'error');
// Check that the modal footer's background color is red
this.client.search('.c3-modal-footer.error').cssProperty('background-color').assert('toEqual', 'rgb(240, 0, 0)');
// Close the modal
this.client.click('.modal-close');
// Set the value for the third field of the form
this.client.search('.c3-form-group:nth-of-type(3) .c3-form-field').setValue('test-password');
// Set the value for the fourth field of the form
this.client.search('.c3-form-group:nth-of-type(4) .c3-form-field').setValue('u');
// Check that the autocomplete list is visible
this.client.search('.autocomplete-items').visible().assert('toEqual', true);
// Select the United States of America
this.client.search('.autocomplete-items').searchForElementWithText('div', 'United States of America').click();
// Check that the value of the Country is United States of America
this.client.search('.c3-form-group:nth-of-type(4) .c3-form-field').getValue().assert('toEqual', 'United States of America');
// Submit the form
this.client.search('.c3-button-primary').click();
// Check that the form submitted successfully
this.client.search('.c3-modal-footer.success').visible().assert('toEqual', true);