Imagine a scenario where we have a new tag (my-app) in HTML and need to create a login case using the webdriverIO
.
This is the HTML structure:
To avoid repetitive code, we can utilize the component object pattern. This pattern aims to minimize repetition and centralize the component's API into its object. To interact with an element's shadow DOM, we first require the host element. Utilizing a base class for component objects simplifies this process.
class Component {
constructor(host) {
const selectors = [];
// Traverse back to the browser object and store all selectors
while (host.elementId && host.parent) {
selectors.push(host.selector);
host = host.parent;
}
selectors.reverse();
this.selectors_ = selectors;
}
get host() {
// Starting from the browser object, reselect each element
return this.selectors_.reduce((element, selector) => element.$(selector), browser);
}
}
module.exports = Component;
Subsequently, we create a subclass for our app-login component:
const Component = require('./component');
class Login extends Component {
get usernameInput() {
return this.host.shadow$('input #username');
}
get passwordInput() {
return this.host.shadow$('input[type=password]');
}
get submitButton() {
return this.login.shadow$('button[type=submit]');
}
login(username, password) {
this.usernameInput.setValue(username);
this.passwordInput.setValue(password);
this.submitButton.click();
}
}
module.exports = Login;
Now, we implement the component object within our login page object:
const Login = require('./components/login');
class LoginPage {
open() {
browser.url('/login');
}
get app() {
return browser.$('my-app');
}
get loginComponent() {
// Create a new instance of our login component object
return new Login(this.app.$('app-login'));
}
}
Using this approach enables us to easily integrate the component object into tests for any part of our app utilizing the app-login web component. If changes are made to the internal structure of the web component, only the component object needs updating.
A similar methodology is applied to the Check Box Component by incorporating Shadow DOM support:
public class CheckBox extends Component {
public CheckBox(element) {
this.element = element;
}
get checkBoxSelector() {
return this.host.shadow$(element);
}
get void toggle() {
checkBoxSelector().click();
}
get void check() {
if (!isChecked()) {
toggle();
}
}
get void uncheck() {
if (isChecked()) {
toggle();
}
}
get boolean isChecked() {
return checkBoxSelector().isSelected();
}
}
We then introduce a Check Box Controller component to manage the checkbox instances:
const CheckBox= require('./components/CheckBox');
class CheckBoxController{
open() {
browser.url('/login');
}
get checkboxComponent() {
// Verify whether a specific checkbox has been selected or not
let element = browser.$('[id="lpagecheckbox"]');
return new CheckBox(element);
}
}
Note: This serves as a template to guide us towards solving the problem effectively.
Sources: