Skip to main content

_serenity-js

TL;DR - Show Me The Code

3-work-with-frames/code/serenity-js/spec/frames.spec.ts
import { describe, it } from '@serenity-js/playwright-test';

import { Ensure, equals, not } from '@serenity-js/assertions';
import { notes } from '@serenity-js/core';
import { By, Clear, Enter, Navigate, PageElement, Switch, Text } from '@serenity-js/web';

describe('Frames', () => {

describe('Example 1', () => {

const frames = {
top: PageElement.located(By.css('[name="frame-top"]')),
middle: PageElement.located(By.css('[name="frame-middle"]')),
content: PageElement.located(By.id('content')),
};

it('works with nested frames', async ({ actor }) => {

await actor.attemptsTo(
Navigate.to('https://the-internet.herokuapp.com/nested_frames'),
Switch.to(frames.top).and(
Switch.to(frames.middle).and(
Ensure.that(Text.of(frames.content), equals('MIDDLE')),
),
),
);

});
});

describe('Example 2', () => {

interface EditorText {
before: string;
after: string;
}

const tinyMceComponent = {
heading: PageElement.located(By.css('h3')),
frame: PageElement.located(By.id('mce_0_ifr')),
editor: PageElement.located(By.id('tinymce')),
}

it('works with a TinyMCE WYSIWYG Editor', async ({ actor }) => {

await actor.attemptsTo(
Navigate.to('https://the-internet.herokuapp.com/tinymce'),
Switch.to(tinyMceComponent.frame).and(
notes<EditorText>().set('before', Text.of(tinyMceComponent.editor)),

Clear.theValueOf(tinyMceComponent.editor),
Enter.theValue('Hello World!').into(tinyMceComponent.editor),
notes<EditorText>().set('after', Text.of(tinyMceComponent.editor)),

Ensure.that(notes<EditorText>().get('before'), not(equals(notes<EditorText>().get('after')))),
Ensure.that(notes<EditorText>().get('before'), equals('Your content goes here.')),
Ensure.that(notes<EditorText>().get('after'), equals('Hello World!')),
),
Ensure.that(Text.of(tinyMceComponent.heading), equals('An iFrame containing the TinyMCE WYSIWYG Editor')),
);
});
});
});

Code Walkthrough

This example uses Serenity/JS and Playwright Test to demonstrate how to work with frames in a web application.

To learn more about Serenity/JS, check out:

Importing Libraries, Setup and Teardown

First, we import the describe and it functions from @serenity-js/playwright-test - the Serenity/JS adapter for Playwright Test. We'll use these functions to group and define our test cases, respectively, as they offer integration with Serenity/JS reporters and test fixtures that allow us to use Serenity/JS Screenplay Pattern libraries in our test scenarios. Relying on Serenity/JS fixtures enables us to avoid defining the setup and teardown logic for our tests, as it's all done automatically by the test runner.

Later, we import Serenity/JS Screenplay Pattern libraries that we'll use to interact with the web app and perform assertions. Note that the @serenity-js/web module offers an abstraction layer over web integration tools like Selenium, Playwright, or WebdriverIO, allowing us to write our tests in a driver-agnostic way and swap out the driver without having to modify the tests. Furthermore, the @serenity-js/assertions module provides an universal assertion library that enables us to write our assertions using the same syntax regardless of the interface our tests interact with - be it web UI, REST API, or anything else.

Example 1

Our first test scenario is interacting with the nested frames example from the-internet.

While Selenium, WebdriverIO, and Playwright all approach frame switching differently and have their own methods for it, Serenity/JS provides a single, consistent API for working with frames, regardless of the underlying driver.

Afterwards, to perform activities in the context of a frame, we use the interaction to Switch from the @serenity-js/web module, and provide a PageElement that represents the frame we want to work with. When our actor finishes their last activity in the frame, they'll automatically switch their focus back to the parent frame or the top-level page, depending on the context. This way, we can avoid the need to keep track of frames and manually switch back after we're done with the nested frame.

If we need to work with frames nested in other frames, like the middle frame in this example, we repeat the same pattern to get to the desired frame, then retrieve the text of the desired element, to then finally perform the assertion. When the actor is done, they'll automatically switch their focus back to the top-level frame, and then the top-level page.

Example 2

A common scenario where you'll likely run into frames involves working with WYSIWYG editors, such as TinyMCE.

Our second test scenario is interacting with the TinyMCE editor embedded in a nested frame. You'll notice that apart from interacting with the frame, this example also demonstrates how to make you actor use their notes() to store and retrieve information about the state of the application under test.

Once the page loads:

  • We switch into the frame that contains TinyMCE
  • Then, store the original text displayed in the editor and store it in the actor's notes
  • Next, use the interaction to Clear to remove the contents of the editor
  • Afterward, use the interaction to Enter to enter the new value
  • Finally, use the interaction to Ensure to assert that the new value is different than what the actor saw originally

When the activity sequence from is complete, the actor automatically switches their focus back to the top-level page. This mechanism allows us to go straight to the next step of the scenario without having to worry about the frames, and so it ensures that the content of the top-level page heading is as expected.

Executing the Test

Before executing the test, we need to make sure the required dependencies are declared on the package.json file.

Toggle to see the package.json file.
3-work-with-frames/code/serenity-js/package.json
{
"name": "serenity-js-elemental-automation-work-with-frames",
"version": "1.0.0",
"description": "",
"scripts": {
"test": "playwright test",
"postinstall": "playwright install --with-deps"
},
"license": "MIT",
"homepage": "https://serenity-js.org",
"dependencies": {
"@playwright/test": "1.47.2",
"@serenity-js/assertions": "3.29.3",
"@serenity-js/core": "3.29.3",
"@serenity-js/console-reporter": "3.29.3",
"@serenity-js/playwright-test": "3.29.3",
"@serenity-js/web": "3.29.3"
},
"devDependencies": {
"@types/node": "20.16.10",
"typescript": "5.6.3"
}
}

Finally, we can run the test by executing npm test from the command-line.