Site icon ELEKS: Enterprise Software Development, Technology Consulting

Addressing The Limitations of Cypress Framework with Playwright and Selenium Injections

Article

Addressing The Limitations of Cypress Framework with Playwright and Selenium Injections

Cypress framework falls short when it comes to handling multi-tabs or multi-windows cases. However, what if we find ourselves in a situation where we must retain our tests in Cypress and yet need to automate scenarios beyond its capabilities? Read on to explore some innovative practices for expanding basic Cypress functionality through Selenium or Playwright.

Without a doubt, the automation testing framework is crucial for all automation testers. It provides an unparalleled level of simplification to both test development and execution activities. Our team at ELEKS have extensive experience with software testing and QA services, utilising a variety of different tools and automation frameworks. We've also published several articles on the topic of E2E testing, including a comparison of Playwright vs Cypress, as well as tips for improving your E2E tests with playwright automation. In case you missed them, here are the links:

You might wonder “If Cypress framework lacks support for multi-tabs and multi-window cases, why not consider alternatives like Playwright or Selenium?” It’s a valid question, indeed. Both Playwright and Selenium offer the added advantage of operating outside the browser and providing robust APIs for seamless communication. Moreover, opting for Playwright or Selenium would prove easier and more beneficial in terms of maintaining a coherent framework. Mixing different frameworks can unnecessarily complicate matters, leading to intricate code structures and unreliable tests.

This article is aimed at individuals facing limited options, where they need to primarily utilize Cypress for their tests but also require additional tools to address scenarios that Cypress automation tool does not support. ELEKS Senior Test Automation Engineer, Oleksandr Chako, provides valuable insights into incorporating Playwright or Selenium injections into Cypress tests.

It is crucial to carefully consider whether automating a particular case is truly necessary or if alternative workarounds have already been explored. If the answer is “Yes”, then employing Playwright or Selenium injections in your Cypress tests is likely the way to go.

How to setup Cypress framework for Selenium and Playwright injections

Cypress is a framework, which means we can't use third-party tools inside it unless it is permitted by the Cypress developer's team. In our code, we cannot utilise third-party API methods; instead, Cypress expects us to use its own API methods within the "describe" or "it" blocks. However, the Cypress developers have provided a workaround called "cy.task()" which we can leverage. This method enables us to execute any NodeJS command inside Cypress. For detailed information, you can refer to the official Cypress website's documentation on the "task" command.

We will be using Playwright, and Selenium, which all have JS versions, for our testing purposes. These tools are also widely popular in testing world. Regardless of the tool selected, the underlying principle will remain the same.

To illustrate, let's consider creating the same test using Cypress, Playwright, and Selenium. We will launch these tests in separate "it" blocks but within a single "describe" block.

However, before we proceed, we need to set up the foundation of our Cypress project.

Setting up Cypress project

The project architecture will have the following structure:

If you are familiar with Cypress, you'll find this architecture as simple as it could be, and it's quite enough for our purposes. Let's look at a basic test case - navigating to wikipedia.org, typing something in the search field and asserting the title of the resulting page. In Cypress, this would look like this:

// wiki.cy.ts

describe("Extended tests", () => {
it("cypress test", () => {
cy.visit("https://www.wikipedia.org/");
cy.get("#searchInput").clear().type("Stranger in a Strange Land");
cy.get('button[type="submit"]').click();

cy.get('h1#firstHeading').should('have.text', 'Stranger in a Strange Land')
});
});

Now is the time to launch our code and ensure everything runs smoothly. Afterwards, we can proceed with translating this example into Selenium and perform our first injection.

Selenium injections

In this section, we will focus on creating the initial Selenium method. It's important to note that we are only creating a method, not a test. If you are acquainted with the Selenium automation tool, you might be accustomed to running tests using a mocha test runner. However, the Cypress framework already provides a test runner. Therefore, our objective is to create a method specifically for executing the Selenium script.

To begin, let's establish a new folder at the root and name it "Selenium." Within this folder, we will create a file called "selenium-test.ts" and proceed with coding the necessary implementation.

// selenium-test.ts

import { By, Builder, selenium } from 'selenium-webdriver';
import { expect } from 'chai';

export default async function () {
const driver = await new Builder().forBrowser('chrome').build();

await driver.get('https://www.wikipedia.org/');

await driver.findElement(By.css('#searchInput')).sendKeys('Stranger in a Strange Land');
await driver.findElement(By.css('button[type="submit"]')).click();

const innerText = await driver.findElement(By.css('h1#firstHeading')).getText();
expect(innerText).to.equal('Stranger in a Strange Land');

await driver.quit();

return null;
}

In this part of the code, we will launch an additional browser instance, navigating to the URL, and do some actions. After that, close the browser and return a “null” value. It’s important to note that any method launching with “cy.task()” should return something in order for it to work properly.

Another important thing is that we can't run Selenium tests in the same browser session with Cypress. It means we can't run Cypress tests and then switch to Selenium methods, and then switch back to Cypress. The code into the “cy.task()” will be runned into a separate NodeJS process. When using Selenium or Playwright injections, it implies that we have separate logic that will be launched in a distinct browser instance.

Now, let's define a Selenium task for Cypress. To do this, navigate to the "cypress.config.ts":

// cypress.config.ts

import { defineConfig } from 'cypress';

export default defineConfig({
e2e: {
setupNodeEvents(on: Cypress.PluginEvents, config: Cypress.PluginConfigOptions) {
on('task', {
async selenium(testPath:string) {
const { default: defaultExport } = await import(`./selenium/${pathToTest}`);
return defaultExport();
}
});
}
}
});

Within this task, our objective is to define the path for testing and executing the default method of the module. If we have more than one Selenium test, we need to place it to separate files. Then, we can use one task and specify the path to this test with the argument. Alternatively, we can have one file with a few Selenium methods. In this case, we need to specify separate tasks with hardcoded names for each method.

With these preparations complete, we can now proceed to inject Selenium into the Cypress test:

// wiki.cy.ts

describe('Extended tests', () => {
it('cypress test', () => {
cy.visit('https://www.wikipedia.org/');
cy.get('#searchInput').clear().type('Stranger in a Strange Land');
cy.get('button[type='submit']').click();

cy.get('h1#firstHeading').should('have.text', 'Stranger in a Strange Land')
});

it('selenium test', () => {
cy.task('selenium','selenium-test');
});
});

This code will launch a task with the name “selenium” and pass into it argument “selenium-test”, which is the name of a file with our Selenium method.

Playwright injections

We can apply the same procedure to Playwright. Let's create a new folder in the root and call it “Playwright”, inside this folder create “playwright-test.ts”:

// playwright-test.ts

import { LaunchOptions, chromium } from 'playwright';
import { expect } from '@playwright/test';

const playwrightOptions: LaunchOptions = {
headless: false
};

export default async function () {
const browser = await chromium.launch(playwrightOptions);
const context = await browser.newContext();
const page = await context.newPage();

await page.goto('https://www.wikipedia.org/');

await page.type('#searchInput', 'Stranger in a Strange Land');
await page.click('button[type="submit"]');

const title = await page.innerText('h1#firstHeading');
expect(title).toEqual('Stranger in a Strange Land');

await browser.close();

return null;
}

Then add a new “task” in “cypress.config.ts”:

// cypress.config.ts

import { defineConfig } from 'cypress';

export default defineConfig({
e2e: {
setupNodeEvents(on: Cypress.PluginEvents, config: Cypress.PluginConfigOptions) {
on('task', {
async selenium(testPath:string) {
const { default: defaultExport } = await import(`./selenium/${testPath}`);
return defaultExport();
}
});

on('task', {
async playwright(testPath:string) {
const { default: defaultExport } = await import(`./playwright/${testPath}`);
return defaultExport();
}
});
}
}
});

and “wiki.cy.ts”:

// wiki.cy.ts

describe('Extended tests', () => {
it('cypress test', () => {
cy.visit('https://www.wikipedia.org/');
cy.get('#searchInput').clear().type('Stranger in a Strange Land');
cy.get("button[type='submit']").click();

cy.get('h1#firstHeading').should('have.text', 'Stranger in a Strange Land')
});

it('selenium test', () => {
cy.task('selenium','selenium-test');
});

it('playwright test', () => {
cy.task('playwright','playwright-test');
});
});

Once all the necessary implementations are completed, the updated architecture should look like this:

Identifying use cases for Selenium and Playwright injections in Cypress tests

In my practice, I found the most profitable this approach for cases when we have some kind of third-party integrations. For example, we need to do some stuff in one application, then switched to another with multi-tabs cases, do necessary actions with Selenium, and then back to the first application and assert that something changed.

Another probably working case is cross-browser testing. Cypress doesn't have at the moment supporting WebKit, but Playwright does. We can use Playwright injection to automate WebKit cases.

Addressing the key obstacles of executing injections in Cypress

When performing injections in Cypress, it is important to acknowledge that this task adds an additional layer of complexity to your test library. As a result, it becomes more intricate and challenging to maintain.

Using methods inside “cy.task” are considerably harder to debug compared to the native methods provided by Cypress. This difficulty arises from the lack of access to these methods through the Cypress UI launcher. To debug something, you would need to return the desired value and invoke it within the Cypress process:

// task code

export default async function () {
const driver = await new Builder().forBrowser('chrome').build();

await driver.get('https://www.wikipedia.org/');

await driver.findElement(By.css('#searchInput')).sendKeys('Stranger in a Strange Land');
await driver.findElement(By.css('button[type="submit"]')).click();

const innerText = await driver.findElement(By.css('h1#firstHeading')).getText();

await driver.quit();

return innerText;
}


// test code

it("selenium test", () => {
cy.task("selenium", 'selenium-test').then(result=>{
console.log(result)
})
});

We can expand the capabilities of Cypress and achieve our automation objectives, but this comes at the expense of adding complexity to our test library. For more detailed information on this, you can refer to the project available on Oleksandr Chako's Github repository.

Key takeaways

Although Cypress may have limitations, there are ways to work around them if the situation demands it. It's crucial that you carefully consider whether automating a specific scenario is essential or if other alternatives can be explored before making any decisions. If your answer is yes, then leveraging Selenium or Playwright injections within Cypress tests could be an effective solution for you.

In several use cases, injecting these tools into your testing process can bring significant benefits - such as when dealing with third-party integrations and cross-browser compatibility issues. However, we must acknowledge that using injections in Cypress does add complexity to the test library and might make debugging more challenging than usual.

Exit mobile version