SDEThub.com https://sdethub.com/ Test automation tutorials, guides, and useful materials to improve your automation skills Sun, 13 Oct 2024 14:10:44 +0000 en-US hourly 1 https://wordpress.org/?v=6.7.1 https://i0.wp.com/sdethub.com/wp-content/uploads/2023/12/cropped-Frame-2-1.png?fit=32%2C32&ssl=1 SDEThub.com https://sdethub.com/ 32 32 230280074 How To Apply A Custom CSS While Making The Screenshots In Playwright Tests https://sdethub.com/eugene-truuts/use-a-custom-css-in-playwright-screenshots/ https://sdethub.com/eugene-truuts/use-a-custom-css-in-playwright-screenshots/#respond Sun, 25 Feb 2024 15:50:52 +0000 https://sdethub.com/?p=261 In Playwright v1.41.0 the new option stylePath was added for the toHaveScreenshot() method to apply a custom stylesheet while making the screenshot. Now you can apply a custom stylesheet (.CSS file) to your page while taking screenshots. This allows filtering out dynamic or volatile elements, hence improving the screenshot determinism. In this lesson we will […]

The post How To Apply A Custom CSS While Making The Screenshots In Playwright Tests appeared first on SDEThub.com.

]]>

In Playwright v1.41.0 the new option stylePath was added for the toHaveScreenshot() method to apply a custom stylesheet while making the screenshot.

Now you can apply a custom stylesheet (.CSS file) to your page while taking screenshots. This allows filtering out dynamic or volatile elements, hence improving the screenshot determinism.

In this lesson we will use the Facebook stories section which we had already tested in one of the previous lessons.

Let’s imagine we want to test the layout, so we don’t actually need the dynamic parts such as pictures inside the story’s containers as well as the usernames. And of course, we don’t want to see the fresh-baked venison photo in my own story 😆.

So, the first thing we have to do is create a new stylesheet file in our test project, let’s say src/styles/style.css.

In style.css, we can define the new styles that will be applied during the screenshot assertion. In our case, I think we could hide the images, story usernames, and some other elements in the “What’s on your mind?” section.

CSS
[aria-label="Stories"] img,
[aria-label="Stories"] span,
[aria-label="Create a post"] img,
[aria-label="Create a post"] span {
    visibility: hidden;
}

Now you can add the path to style.css in your screenshot assertion method:

TypeScript
test('Playwright CSS test', async ({page}) => {
    await page.goto('https://facebook.com');
    await expect(page).toHaveScreenshot('facebook', { stylePath: `${__dirname}/../styles/style.css` });
});

Now the screenshot method is filtering out our dynamic elements from the screenshot file.

It seems like a pretty simple and accurate approach to asserting the screenshots in visual testing of pages with dynamic elements.

You can still hide the element using the page.evaluate() method by executing JavaScript in the browser’s console as well as using the element masking. Check out the ‘How to Mask Dynamic Elements in Automated Visual Tests’ guide.

The post How To Apply A Custom CSS While Making The Screenshots In Playwright Tests appeared first on SDEThub.com.

]]>
https://sdethub.com/eugene-truuts/use-a-custom-css-in-playwright-screenshots/feed/ 0 261
How To Control a Keyboard in Playwright https://sdethub.com/eugene-truuts/how-to-control-a-keyboard-in-playwright/ https://sdethub.com/eugene-truuts/how-to-control-a-keyboard-in-playwright/#comments Sun, 07 Jan 2024 18:20:36 +0000 https://sdethub.com/?p=214 Form filling, text input, and hotkey pressing are crucial parts of user interface (UI) test automation. In this guide, we will study how to use keyboard events using Playwright. Keyboard.press() To simply press any keyboard key you can use the page.keyboard.press() method. It only executes keyDown and keyUp events: You can also add the timeout […]

The post How To Control a Keyboard in Playwright appeared first on SDEThub.com.

]]>

Form filling, text input, and hotkey pressing are crucial parts of user interface (UI) test automation. In this guide, we will study how to use keyboard events using Playwright.

Keyboard.press()

To simply press any keyboard key you can use the page.keyboard.press() method. It only executes keyDown and keyUp events:

TypeScript
await page.keyboard.press('A');

You can also add the timeout to wait between keyDown and keyUp in milliseconds adding the delay option:

TypeScript
await page.keyboard.press('A', {delay:1000});

Playwright supports not only the characters but also the modifiers and functional keys like Enter or Arrow:

TypeScript
await page.keyboard.press('Enter');

To avoid typos in big projects, I suggest storing all keys in one place:

TypeScript
export const KEYBOARD_KEY = {
    A: 'a',
    ALT: 'Alt',
    ARROW_DOWN: 'ArrowDown',
    B: 'b',
    C: 'c',
    CONTROL: 'Control',
    D: 'd',
    // ...
};

await page.keyboard.press(KEYBOARD_KEY.ARROW_DOWN);

Keyboard.press() shortcuts

Of course, you can use key combinations, for example, to copy-paste something. There are two ways to do that.

Use the keyboard.down() and the keyboard.up() methods combination to hold the Control button, then press the C button, and then release the Control button:

TypeScript
export const KEYBOARD_KEY = {
    A: 'a',
    ALT: 'Alt',
    ARROW_DOWN: 'ArrowDown',
    B: 'b',
    C: 'c',
    CONTROL: 'Control',
    D: 'd',
    // ...
};

    await page.keyboard.down(KEYBOARD_KEY.CONTROL);
    await page.keyboard.press(KEYBOARD_KEY.C);
    await page.keyboard.up(KEYBOARD_KEY.CONTROL);

Or just use the KEY+KEY combination.

TypeScript
export const KEYBOARD_KEY = {
    A: 'a',
    ALT: 'Alt',
    ARROW_DOWN: 'ArrowDown',
    B: 'b',
    C: 'c',
    CONTROL: 'Control',
    D: 'd',
    // ...
};

await page.keyboard.press(`${KEYBOARD_KEY.CONTROL}+${KEYBOARD_KEY.C}`);

It is my favorite common approach to use keyboard shortcuts.

Learn all key values for keyboard events here.

Multiplatform keyboard.press() shortcuts

One of the common problems I faced at the beginning of my automation journey was the fact that some keys in MacOS and Windows/Linux are different. For example, there is no Control key on MacOS because there is a Meta (CMD or Command) key instead. It was a problem because my tests run on both Windows and MacOS platforms.

TypeScript
await page.keyboard.press('Control+C'); //Windows or Linux
TypeScript
await page.keyboard.press('Meta+C'); //MacOS

Fortunately, there is a pretty simple solution to handle it. The only thing you need is to detect the platform. In JavaScript, you can do this using an os.platform() method:

TypeScript
const os = require('os');
const platform = os.platform();

Since your test is now able to detect the platform, you can add a logic in KEYBOARD_KEY const based on the detected platform:

TypeScript
const os = require('os');
const platform = os.platform();

export const KEYBOARD_KEY = {
    A: 'a',
    ALT: 'Alt',
    ARROW_DOWN: 'ArrowDown',
    B: 'b',
    C: 'c',
    CONTROL: platform === 'darwin' ? 'Meta' : 'Control',
    D: 'd',
    // ...
};

await page.keyboard.press(`${KEYBOARD_KEY.CONTROL}+${KEYBOARD_KEY.C}`);

If this code is executed on the Darwin (MacOS) platform, the Meta+C will be pressed and Control+C will be pressed in other cases (Windows or Linux).

Keyboard.type()

This Playwright method sends a keyDownkeyPress/input, and keyUp event for each character in the text.

TypeScript
await page.keyboard.type('Hello SDEThub.com');

Keyboard.type() also supports delay to type text with user-like speed:

TypeScript
await page.keyboard.type('Hello SDEThub.com', { delay: 100 });

In most cases, I suggest using locator.fill() and locator.pressSequentially() instead. Please, read the appropriate guide here:

Keyboard modifiers, keyboard.up(), and keyboard.down()

One of the most popular keyboard modifiers is SHIFT. It is really simple to use it in Playwright using the keyboard.down() and the keyboard.up() methods combination.

TypeScript
export const KEYBOARD_KEY = {
    A: 'a',
    ALT: 'Alt',
    ARROW_DOWN: 'ArrowDown',
    B: 'b',
    C: 'c',
    CONTROL: 'Control',
    D: 'd',
    SHIFT: 'Shift',
    // ...
};

    await page.keyboard.down(KEYBOARD_KEY.SHIFT);
    await page.locator('//*[@id = "name"]').fill('Hello SDEThub.com');
    await page.keyboard.up(KEYBOARD_KEY.SHIFT);

Don’t forget to release the SHIFT button using the keyboard.up() method.

The post How To Control a Keyboard in Playwright appeared first on SDEThub.com.

]]>
https://sdethub.com/eugene-truuts/how-to-control-a-keyboard-in-playwright/feed/ 1 214
locator.fill and locator.pressSequentially in Playwright https://sdethub.com/eugene-truuts/locator-fill-and-locator-presssequentially-in-playwright/ https://sdethub.com/eugene-truuts/locator-fill-and-locator-presssequentially-in-playwright/#comments Thu, 04 Jan 2024 21:11:37 +0000 https://sdethub.com/?p=205 In Playwright 1.38, a new locator.pressSequentially() method was introduced. On the other hand, it still has the locator.fill() method. Both methods serve one simple goal – to complete text input with text using a keyboard. This article will briefly examine how all these methods work and when to use them. For this tutorial, I will […]

The post locator.fill and locator.pressSequentially in Playwright appeared first on SDEThub.com.

]]>

In Playwright 1.38, a new locator.pressSequentially() method was introduced. On the other hand, it still has the locator.fill() method. Both methods serve one simple goal – to complete text input with text using a keyboard.

This article will briefly examine how all these methods work and when to use them.

For this tutorial, I will use a simple HTML user input.

You can generate it for test purposes using the Playwright Page.setContent() method:

TypeScript
await page.setContent('
<!DOCTYPE html>
<html>
<body>

<form action="/action_page.php">
  <label for="fname">First name:</label>
  <input type="text" id="name"
</form>

</body>
</html>

');

locator.fill()

Let’s start with the page.fill() method. It waits for actionability checks, focuses on the element, fills it, and triggers an input event after filling. You can also pass an empty string to clear the input field.

Fills the input element with one action as if you pasted it from the clipboard.

TypeScript
await page.locator('//*[@id = "name"]').fill('Eugene Truuts');

I suggest this filling method as default when you just want to fill the forms without checking any user input events handling. To send fine-grained keyboard events, use a locator.pressSequentially() method

locator.pressSequentially()

This method focuses on the element and then sends a keydown, keypress/input, and keyup event for each character in the text to simulate the real-like user’s behavior.

TypeScript
await page.locator('//*[@id = "name"]').pressSequentially('Eugene Truuts');

I suggest using it to test specific user input interactions like search fields with user input events handling. I.e, then you want to check that search results refresh after each entered character just like on the Google search page.

locator.type

In earlier versions of Playwright (< 1.38) locator.type() method used to make the same things as the locator.pressSequentially() method.

TypeScript
await page.locator('//*[@id = "name"]').type('Eugene Truuts');

In Playwright 1.38 locator.type() method was deprecated. Use the locator.pressSequentially() method instead.

Check my Playwright keyboard guide here:

The post locator.fill and locator.pressSequentially in Playwright appeared first on SDEThub.com.

]]> https://sdethub.com/eugene-truuts/locator-fill-and-locator-presssequentially-in-playwright/feed/ 1 205 How the Screenshots Naming Works in Playwright https://sdethub.com/eugene-truuts/how-the-screenshots-naming-works-in-playwright/ https://sdethub.com/eugene-truuts/how-the-screenshots-naming-works-in-playwright/#respond Sat, 18 Nov 2023 22:04:05 +0000 https://sdethub.com/?p=14 Using screenshots is a crucial part of visual testing in Playwright. When you have a lot of tests and test files in your testing project, keeping your gold screenshots in order is essential. One of the popular questions I got from my colleagues is, “Can I somehow change the directory for saving screenshots in Playwright?”. […]

The post How the Screenshots Naming Works in Playwright appeared first on SDEThub.com.

]]>

Using screenshots is a crucial part of visual testing in Playwright. When you have a lot of tests and test files in your testing project, keeping your gold screenshots in order is essential.

One of the popular questions I got from my colleagues is, “Can I somehow change the directory for saving screenshots in Playwright?”. To answer it, I’ll explain the built-in screenshot path template structure in Playwright and then show you how to change the screenshot path and include helpful details in filenames using the snapshotPathTemplate configuration option.

Default screenshots directory in Playwright

By default, Playwright wants you to keep screenshot files in a directory whose name includes your test file name. For instance, if you assert the screenshot in file /tests/example.spec.ts, your gold screenshot files should be stored in the /tests/example.spec.ts-snapshots directory.

The default file name should also include the project and the platform: example-chromium-darwin.png

Keep in mind that you don’t need to include the project and the platform in the test code:

test.spec.ts
await expect(page).toHaveScreenshot('example.png')

This is because the project and platform are dynamic and depend on the environment you are running the tests. If you have multiple projects, i.e., Chromium and Firefox, your test code will be the same (‘example.png’), but you need two different files with different projects in their names accordingly:

example-chromium-darwin.png and example-firefox-darwin.png.

Playwright screenshot path settings

Sometimes, the default approach to file naming does not fit your project structure. However, you can modify it using the playwright.config.ts configuration file you can find in the root directory of your Playwright test project.

All test screenshot path properties can be set in the snapshotPathTemplate option. This option configures a template controlling the location of snapshots generated by pageAssertions.toHaveScreenshot(name[, options]) and snapshotAssertions.toMatchSnapshot(name[, options])

The value might include some “tokens” that will be replaced with actual values during test execution.

For example, the default example.spec.ts-snapshots/example-chromium-darwin.png naming is a result of using the token structure like this:

playwright.config.ts
snapshotPathTemplate: '{testDir}/{testFilePath}-snapshots/{arg}-{projectName}-{platform}{ext}'

In this example, multiple default tokens are used:

testDir: Default project’s testConfig.testDir.

testFilePath: Relative path from testDir to test file.

arg: Relative snapshot path without extension. These come from the arguments passed to the toHaveScreenshot() and toMatchSnapshot() calls; if called without arguments, this will be an auto-generated snapshot name.

projectName: Project’s file-system-sanitized name, if any.

platform: The value of process.platform.

ext: snapshot extension (with dots).

Change the default screenshot directory.

The most common change you could want to make in a test project is a different or separate directory, which doesn’t depend on the test file. It is a pretty simple task:snapshotPathTemplate:

playwright.config.ts
snapshotPathTemplate: 'snapshots/{arg}-{projectName}-{platform}{ext}'

By using such a configuration, your screenshots have to be stored in the project root /snapshots directory (/snapshots/example-chromium-darwin.png)

Exclude the platform and project name.

Suppose you run your tests on only one platform or project, or your goal is pixel-perfect on every platform and browser. In that, you can also exclude appropriate tokens from the snapshotPathTemplate option:

playwright.config.ts
snapshotPathTemplate: 'snapshots/{arg}{ext}'

The result of such a configuration will be /snapshots/example.png

Add Platform and Browser name-based structure

You can also introduce different file structures based on platform and project by using / separators:

playwright.config.ts
snapshotPathTemplate:'snapshots/{projectName}/{platform}/{arg}{ext}'

By providing this configuration, your screenshot will stored in the platform/project structure: snapshots/chromium/darwin/example.png

Or add the test file name in:

playwright.config.ts
snapshotPathTemplate: 'snapshots/{projectName}/{testFilePath}-{arg}{ext}'

In this case, your files should be stored in /snapshots/chromium/example.spec.ts-example.png

Include the test title.

Sometimes, if you want to include the test title in the screenshot name, you can use the {testName} token. Let’s check some example test:

test.spect.ts
test('TC01 Open LinkedIn URL @smoke', async ({page}) => {
  await page.goto("https://www.linkedin.com/in/truuts");
  await expect(page).toHaveScreenshot('example.png')
});

To include the test title in a snapshot path template, use the testName token.

testName: File-system-sanitized test title, including parent describes but excluding file name.

playwright.config.ts
snapshotPathTemplate: 'snapshots/{testName}/{projectName}/{arg}{ext}'

The resulting path will be: /snapshots/TC01-Open-LinkedIn-URL-smoke/chromium/example.png

I hope this tutorial helps you to structure your screenshots in your way and optimize your test project. Happy testing!

The post How the Screenshots Naming Works in Playwright appeared first on SDEThub.com.

]]>
https://sdethub.com/eugene-truuts/how-the-screenshots-naming-works-in-playwright/feed/ 0 14
How To Add Tags Dynamically In Playwright Tests https://sdethub.com/eugene-truuts/how-to-add-tags-dynamically-in-playwright-tests/ https://sdethub.com/eugene-truuts/how-to-add-tags-dynamically-in-playwright-tests/#comments Fri, 10 Nov 2023 22:39:56 +0000 https://sdethub.com/?p=65 In this brief story, I want to share with you my approach to dynamically tagging Playwright tests. By default, Playwright recommends that you use the — grep and — grep-invert command line flags for that In this example, I have several tests, and two of them are tagged as “@smoke”. To run only smoke tests […]

The post How To Add Tags Dynamically In Playwright Tests appeared first on SDEThub.com.

]]>

In this brief story, I want to share with you my approach to dynamically tagging Playwright tests.

By default, Playwright recommends that you use the — grep and — grep-invert command line flags for that

test.spec.ts
test('TC01 Open LinkedIn URL @smoke', async ({page}) => {
  await page.goto("https://www.linkedin.com/in/truuts");
});

test('TC02 Open Medium URL', async ({page}) => {
  await page.goto("https://truuts.medium.com");
});

test('TC03 Open Facebook URL @smoke', async ({page}) => {
  await page.goto("https://www.facebook.com/eugene.truuts");
});

In this example, I have several tests, and two of them are tagged as “@smoke”. To run only smoke tests you can use the -grep test run flag:

ShellScript
npx playwright test --grep @smoke

This command will run the TC01 and TC03 tests only. You can check it by passing the — list parameter to get the test run listing:

Playwright output
Listing tests:
  [chromium]  example.spec.ts:6:5  TC01 Open LinkedIn URL @smoke
  [chromium]  example.spec.ts:14:5  TC03 Open Facebook URL @smoke

That looks pretty simple in the beginning, but then you have hundreds or even thousands of tests in your project, and managing such tags directly in test titles becomes really tricky. Different tags such as test priority, grouping, and others can be changed in your test case management system, and it is almost impossible to manage all these tags in test code. Instead of keeping your tags in the test code, I suggest dynamically setting tags.

First, let’s introduce a test case list in a separate testCases.ts file. I’m using TypeScript, but this approach can be used with any other language.

testCases.ts
export const testCase = {
    groups: {
        smoke: [
            'TC01',
            'TC03',
        ],
        performance: [
            'TC7890',
            'TC7330',
            'TC7555',
        ],
        
    },
};

You can structure it in your own way and even generate it using your test case management system API like TestRail or something. It could be a JSON file or the constant I used, it is not critical for my approach.

In the next step, I will introduce a method to read this file, check if the running test is in the smoke or performance list by passing the test id and title as arguments, and tag it automatically during the Playwright test run by returning the tagged test title.

test-utilities.ts
export function getTestTitle(testItId: string, title: string) {
    let priority: string;
    switch (true) {
        case testCase.groups.smoke.includes(testItId):
            priority = '@SMOKE';
            break;
        case testCase.groups.performance.includes(testItId):
            priority = '@PERFORMANCE';
            break;
        default:
            break;
    }
    return `${testItId}: ${title}: ${priority}`;
}

Now you can get rid of tags in the test title code and pass our new method directly in test titles:

test.spec.ts
test(getTestTitle('TC01', 'Open LinkedIn URL'), async ({page}) => {
  await page.goto("https://www.linkedin.com/in/truuts");
});

test(getTestTitle('TC02', 'Open Medium URL'), async ({page}) => {
  await page.goto("https://truuts.medium.com");
});

test(getTestTitle('TC01', 'Open Facebook URL'), async ({page}) => {
  await page.goto("https://www.facebook.com/eugene.truuts");
});

As you can see, there are no tags in the test titles, but if you run smoke tests with the — grep flag again, you will get:

Playwright output
Listing tests:
  [chromium] › example.spec.ts:6:5TC01: Open LinkedIn URL: @SMOKE
  [chromium] › example.spec.ts:14:5TC01: Open Facebook URL: @SMOKE

Using this approach to tagging tests dynamically, you have all the test lists in one place. You don’t need to search and modify the tests whenever things change. Only one file should be actual in the entire test project. Using the API, you can even get the lists directly from the test case management system.

Save your time for more important things such as stabilization and speeding of your tests. Happy testing!

The post How To Add Tags Dynamically In Playwright Tests appeared first on SDEThub.com.

]]>
https://sdethub.com/eugene-truuts/how-to-add-tags-dynamically-in-playwright-tests/feed/ 1 65
How To Interact With Sliders Using Playwright https://sdethub.com/eugene-truuts/how-to-interact-with-sliders-using-playwright/ https://sdethub.com/eugene-truuts/how-to-interact-with-sliders-using-playwright/#respond Sun, 29 Oct 2023 21:40:00 +0000 https://sdethub.com/?p=81 Sometimes, you need to deal with sliders in your tests. Playwright does not have any built-in tools to interact with sliders. However, it is not a problem at all. In this guide, you will learn how to interact with sliders in Playwright. Let’s start by adding a simple slider element to our page using the […]

The post How To Interact With Sliders Using Playwright appeared first on SDEThub.com.

]]>

Sometimes, you need to deal with sliders in your tests. Playwright does not have any built-in tools to interact with sliders. However, it is not a problem at all.

In this guide, you will learn how to interact with sliders in Playwright.

Let’s start by adding a simple slider element to our page using the setContent() method, which internally calls document.write() and adds anything you want in the DOM.

test.spec.ts
import {test, expect, Page} from '@playwright/test';

let page: Page;


const sliderHTML = `<div>
    <p><input type="range" id="slider" min="0" max="100"/></p>
</div>`

test('Slider test', async ({page}) => {
  await page.setContent(sliderHTML);
});

In this case, all you get is the simple range slider on the page.

I suggest making the task more complicated and setting the slider value random. This makes us consider how to interact with the slider depending on its value.

test.spec.ts
import {test, expect, Page} from '@playwright/test';

let page: Page;

function getRandomInt(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}

const sliderHTML = `<div>
    <p><input type="range" id="slider" min="0" max="100" value="${getRandomInt(0, 100)}"/></p>
</div>`

test('Slider test', async ({page}) => {
  await page.setContent(sliderHTML);
  await setSliderValue(page, "//*[@id = 'slider']",75);
});

The solution will include multiple steps:

So, the final task is to set the slider to the given value, let’s say 95%.

  1. Find the slider element on a page.
  2. Detect its current value to be able to click on it.
  3. Move the slider into the final position (95%).

It looks pretty simple. Starting with finding the slider, I will use the XPath locator to find it. Feel free to check my XPath mastering guide.

For this case, I suggest using the following:

test.spec.ts
"//*[@id = 'slider']"

The next step is getting the bounding box of the slider element to determine its position and size.

test.spec.ts
const sliderBound = await page.locator("//*[@id = 'slider']").boundingBox();

The bounding box of an element is the smallest possible rectangle (aligned with the axes of that element’s user coordinate system) that entirely encloses it and its descendants.

The bounding box will help us to click and move the mouse cursor inside that box.

Since the slider pin can have random position, we need to find it actual coordinates. To do this, we need to obtain the current slider value from the HTML. It is possible with using page.evaluate() method.

test.spec.ts
const currentSliderValue = await page.evaluate(`document.evaluate(""//*[@id = 'slider']"", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue.value`);

This method returns the current slider value as a number. This number will help you to calculate the slider pin coordinate. Actually, you need to calculate two points for X and Y coordinates.

Because the slider pin moves horizontally, the X coordinates is crucial for us. To calculate it, use the formula:

test.spec.ts
const targetX = sliderBound.x + (sliderBound.width * currentSliderValue / 100);

To get the Y, just calculate the vertical middle point:

TypeScript
const targetY = sliderBound.y + sliderBound.height / 2;

Next, move the mouse cursor to the calculated point and hold its button:

test.spec.ts
await page.mouse.move(targetX, targetY);
await page.mouse.down();

Now you can move the mouse cursor horizontally to any position you want.

test.spec.ts
await page.mouse.move(
    sliderBound.x + (sliderBound.width * 95) / 100,
    sliderBound.y + sliderBound.height / 2,
);
await page.mouse.up();

Let’s bring all parts together and create a method for setting a wanted value to any slider element by its XPath:

test.spec.ts
async function setSliderValue(page: Page, sliderXPath: string, valueAsPercent: number) {
  // Find the slider element using the provided XPath and obtain its bounding box
  const sliderBound = await page.locator(sliderXPath).boundingBox();

  // Use page.evaluate to obtain the current slider value from the HTML using the same XPath
  const currentSliderValue = await page.evaluate(`document.evaluate("${sliderXPath}", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue.value`);

  // Calculate the target X and Y coordinates for the mouse cursor based on the current slider value
  const targetX = sliderBound.x + (sliderBound.width * currentSliderValue / 100);
  const targetY = sliderBound.y + sliderBound.height / 2;

  // Move the mouse cursor to the calculated position
  await page.mouse.move(targetX, targetY);

  // Simulate a mouse click by pressing the mouse button
  await page.mouse.down();

  // Move the mouse cursor to the desired position by the provided valueAsPercent
  await page.mouse.move(
      sliderBound.x + (sliderBound.width * valueAsPercent) / 100,
      sliderBound.y + sliderBound.height / 2,
  );

  // Release the mouse button to complete the interaction
  await page.mouse.up();
}

Now you can use it in your test just passing the slider locator and a wanted value:

test.spec.ts
import {test, expect, Page} from '@playwright/test';

let page: Page;

function getRandomInt(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}

async function setSliderValue(page: Page, sliderXPath: string, valueAsPercent: number) {
  // Find the slider element using the provided XPath and obtain its bounding box
  const sliderBound = await page.locator(sliderXPath).boundingBox();

  // Use page.evaluate to obtain the current slider value from the HTML using the same XPath
  const currentSliderValue = await page.evaluate(`document.evaluate("${sliderXPath}", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue.value`);

  // Calculate the target X and Y coordinates for the mouse cursor based on the current slider value
  const targetX = sliderBound.x + (sliderBound.width * currentSliderValue / 100);
  const targetY = sliderBound.y + sliderBound.height / 2;

  // Move the mouse cursor to the calculated position
  await page.mouse.move(targetX, targetY);

  // Simulate a mouse click by pressing the mouse button
  await page.mouse.down();

  // Move the mouse cursor to the desired position by the provided valueAsPercent
  await page.mouse.move(
      sliderBound.x + (sliderBound.width * valueAsPercent) / 100,
      sliderBound.y + sliderBound.height / 2,
  );

  // Release the mouse button to complete the interaction
  await page.mouse.up();
}

const sliderHTML = `<div>
    <p><input type="range" id="slider" min="0" max="100" value="${getRandomInt(0, 100)}"/></p>
</div>`

const sliderElementLocator = "//*[@id = 'slider']";
test('Slider test', async ({page}) => {
  await page.setContent(sliderHTML);
  await setSliderValue(page, sliderElementLocator, 75);
});

I hope this guide helped you to understand how to handle the sliders in Playwright.

The post How To Interact With Sliders Using Playwright appeared first on SDEThub.com.

]]>
https://sdethub.com/eugene-truuts/how-to-interact-with-sliders-using-playwright/feed/ 0 81
How To Test REST API In Playwright https://sdethub.com/eugene-truuts/how-to-test-rest-api-in-playwright/ https://sdethub.com/eugene-truuts/how-to-test-rest-api-in-playwright/#respond Sun, 29 Oct 2023 20:15:38 +0000 https://sdethub.com/?p=131 Although Playwright is the web testing tool you use for frontend testing, sometimes your tests need to interact with some APIs, for example, to get test data from the server. For example, it can be an authorization token or something else you want to get and use during the test. In this brief guide, you […]

The post How To Test REST API In Playwright appeared first on SDEThub.com.

]]>

Although Playwright is the web testing tool you use for frontend testing, sometimes your tests need to interact with some APIs, for example, to get test data from the server. For example, it can be an authorization token or something else you want to get and use during the test.

In this brief guide, you will learn how to test API with Playwright built-in tools, including request sending and response parsing.

One of the pros of using the Playwright framework is its ability to work with REST API smoothly without using third-party libraries such as node-fetch. All you need to send requests is the built-in APIRequest class introduced in Playwright v1.16.

This class is used for creating APIRequestContext instances, which can be used for sending web requests. An instance of this class can be obtained via playwright.request:

TypeScript
const context = await request.newContext();

I will use the “reqres.in” service for this guide, which simulates real application scenarios for testing purposes.

Since you’ve added a context for the API requests, introduce the response constant to store the APIResponce. Don’t forget to use the correct HTTP method (POST, GET, PUT, etc). The APIRequest supports any of them.

TypeScript
const response = await context.post('https://reqres.in/api/users', {
        data: {
            "name": "Eugene Truuts",
            "job": "SDET"
        },
        headers: {
            "accept": "application/json"
        }
    });

And then, store its answer in the new “responseJson” constant:

TypeScript
const responseJson = await response.json();

For example, let’s check if the response status code is 201:

TypeScript
expect(response.status()).toEqual(201);

I suggest also adding a custom error message with the additional response information that can be useful in debugging and makes your test reports a little bit more readable:

TypeScript
expect(response.status(), {
    message: `Invalid code ${response.status()} - ${await response.text()}]`,
}).toEqual(200);

After we have checked the response status, we can parse the response object using the JSON.stringify() method and use this parsed data in further test logic. For instance, let’s check that user ID was defined in response:

TypeScript
const userId = JSON.stringify(responseJson.id);
    expect(userId, {
        message: `No user ID in response - ${await response.text()}]`,
    }).toBeDefined()You can also get the different response properties, such as headers, body, status, and others, which you can use in test assertions.

You can also get the different response properties, such as headers, body, status, and others, which you can use in test assertions.

The complete test example:

TypeScript
import {expect, request, test} from '@playwright/test';

const API_BASE_URL = 'https://reqres.in';
const HTTP_RESPONSE = {
    OK: 200,
    CREATED: 201
}

test('POST request example test', async () => {
    const context = await request.newContext();
    const response = await context.post(`${API_BASE_URL}/api/users`, {
        data: {
            "name": "Eugene Truuts",
            "job": "SDET"
        },
        headers: {
            "accept": "application/json"
        }
    });
    const responseJson = await response.json();
    console.log(JSON.stringify(`New user added: ${responseJson.name}, ID: ${responseJson.id}`));
    expect(response.status(), {
        message: `Invalid code ${response.status()} - ${await response.text()}]`,
    }).toEqual(HTTP_RESPONSE.CREATED);
    const userId = JSON.stringify(responseJson.id);
    expect(userId, {
        message: `No user ID in response - ${await response.text()}]`,
    }).toBeDefined()
});

In this lesson, you have learned how to send API requests using Playwright and read the response using JSON.stringify(). Happy testing!

The post How To Test REST API In Playwright appeared first on SDEThub.com.

]]>
https://sdethub.com/eugene-truuts/how-to-test-rest-api-in-playwright/feed/ 0 131
How to Mask Dynamic Elements in Automated Visual Tests https://sdethub.com/eugene-truuts/how-to-mask-dynamic-elements-in-automated-visual-tests/ https://sdethub.com/eugene-truuts/how-to-mask-dynamic-elements-in-automated-visual-tests/#respond Thu, 12 Oct 2023 20:17:52 +0000 https://sdethub.com/?p=133 To achieve reliable visual testing, it’s crucial to prevent unreliable tests by effectively hiding dynamic parts of your application. When dealing with elements prone to change, like ad banners, moving text inputs, GIFs, and user cursors, it’s essential to use dynamic element masking. This means hiding these elements before taking screenshots during testing to avoid […]

The post How to Mask Dynamic Elements in Automated Visual Tests appeared first on SDEThub.com.

]]>

To achieve reliable visual testing, it’s crucial to prevent unreliable tests by effectively hiding dynamic parts of your application.

When dealing with elements prone to change, like ad banners, moving text inputs, GIFs, and user cursors, it’s essential to use dynamic element masking. This means hiding these elements before taking screenshots during testing to avoid false results due to dynamic content.

Let’s use an example and imagine we want to assess the layout of the Facebook Stories section through visual testing.

The challenge here is that the images and titles in the stories tray element will differ whenever you reload the page. This isn’t ideal for visual testing.

Modern testing frameworks provide methods for this. For instance, Playwright lets you hide specific page elements when taking screenshots by completely covering its bounding box with a #FF00FF color.

Let’s try to hide all <img> elements before making a screenshot.

TypeScript
let mask_locator = page.locator("//img")
await page.screenshot({path: 'masked.png', mask: [mask_locator]})

The problem with such an approach is that it completely covers your element bounding box, so since every HTML element is a rectangle, you will see something like that.

Default Playwright masking is excellent for simple rectangle ad banners and buttons, but you want something better and more accurate for complex layouts.

For instance, you can toggle the visibility of images in this section because they aren’t necessary for layout testing. To implement this, please review the script provided below.

TypeScript
// Get all img elements on the page
var imgElements = document.getElementsByTagName('img');

// Loop through each img element and hide it
for (var i = 0; i < imgElements.length; i++) {
    imgElements[i].style.display = 'none';
}
// Get the element with aria-label="stories tray"
var storiesTray = document.querySelector('[aria-label="stories tray"]');
// Check if the stories tray element exists
if (storiesTray) {
    // Get all span elements inside the stories tray
    var spanElements = storiesTray.querySelectorAll('span');
    // Loop through each span element inside the stories tray and replace its text content with "test."
    spanElements.forEach(function(span) {
        span.textContent = 'test';
    });
}

This script hides all <img> elements and changes the story titles into “test” because we don’t need to check the text value, just a layout.

After executing such a script, the section will resemble a clean layout that you can capture in a screenshot and use for comparison in your visual tests.

If you don’t want to hide such elements or want to highlight hidden areas, you can fill it with any color, i.e., #FF00FF, using the next script:

TypeScript
// Get all img elements on the page
const imgElements = document.querySelectorAll('img');

// Loop through each img element and set its background color to red
imgElements.forEach((img) => {
  img.style.backgroundColor = '#FF00FF';
  img.src = ''; // Optional: Remove the image source to hide the original image
});

// Get the element with aria-label="stories tray"
var storiesTray = document.querySelector('[aria-label="stories tray"]');
// Check if the stories tray element exists
if (storiesTray) {
    // Get all span elements inside the stories tray
    var spanElements = storiesTray.querySelectorAll('span');
    // Loop through each span element inside the stories tray and replace its text content with "test"
    spanElements.forEach(function(span) {
        span.textContent = 'test';
    });
}

Embracing this technique ensures stable and trustworthy visual tests, allowing you to detect real issues without distractions from transient visual elements.

The post How to Mask Dynamic Elements in Automated Visual Tests appeared first on SDEThub.com.

]]>
https://sdethub.com/eugene-truuts/how-to-mask-dynamic-elements-in-automated-visual-tests/feed/ 0 133
Four Horsemen of the Flaky Tests https://sdethub.com/eugene-truuts/four-horsemen-of-the-flaky-tests/ https://sdethub.com/eugene-truuts/four-horsemen-of-the-flaky-tests/#respond Sun, 01 Oct 2023 21:55:00 +0000 https://sdethub.com/?p=140 Flaky automation tests can be a bane for QA engineers, introducing uncertainty and undermining the reliability of test suites. Drawing from my experience as an SDET and QA Automation mentor, this article offers practical advice to conquer flakiness. While I’ll provide TypeScript and Playwright examples, these universal tips are applicable in any language and framework, […]

The post Four Horsemen of the Flaky Tests appeared first on SDEThub.com.

]]>

Flaky automation tests can be a bane for QA engineers, introducing uncertainty and undermining the reliability of test suites. Drawing from my experience as an SDET and QA Automation mentor, this article offers practical advice to conquer flakiness. While I’ll provide TypeScript and Playwright examples, these universal tips are applicable in any language and framework, helping you write robust and dependable automation tests. Let’s dive in and ensure your tests stand firm.

1. Avoid the implicit wait methods

Occasionally, you will encounter situations where you must wait for elements to appear in the DOM or for your application to reach a specific state to proceed with your test scenario. Even with modern, intelligent automation frameworks like Playwright’s auto-wait features, there will be instances where you need to implement custom wait methods. For instance, consider a scenario with text fields and a confirmation button. Due to specific server-side behavior, the confirmation button becomes visible only after approximately five seconds of completing the form. In such cases, resisting the temptation of inserting a five-second implicit wait between two test steps is essential.

TypeScript
textField.fill('Some text')
waitForTime(5000)
confirmationButton.click()

Use a smart approach instead and wait for the exact state. It can be the text element appearing after some loading or a loader spinning element disappearing after the current step is successfully done. You can check if you are ready for the next test step.

TypeScript
button.click()
waitFor(textField.isVisible())
textField.fill()

Of course, there are cases where you need to wait for something you cannot check smartly. I suggest adding comments in such places or even adding the reason explanation as a parameter in your wait methods or something like that:

TypeScript
waitForTime(5000, {reason: "For unstable server processed something..."}

An additional profit of using it is the test reports where you can store such explanations so it will be obvious for someone else or even for future you what and why such five-second waiting here.

TypeScript
waitForTime(5000, {reason: "For unstable server processed something..."}
button.click()
waitFor(textField.isVisible())
textField.fill()

2. Use robust and reliable locators to select elements

Locators are a crucial part of automation testing, and everyone knows it. However, despite this simple fact, many automation engineers are bailing on the stability and using something like this in their tests.

TypeScript
//*[@id="editor_7"]/section/div[2]/div/h3[2]

That is a silly approach because the DOM structure is not so static; some different teams can sometimes change it, and e this after your tests fail. So, use the robust locators. You’re welcome to my XPath-related stories reading, by the way.

3. Make tests independent from each other

When running the test suite with multiple tests in a single thread, it’s crucial to ensure each test is independent. This means that the first test should return the system to its original state so the next test doesn’t fail due to unexpected conditions. For example, if one of your tests modifies user settings stored in LocalStorage, clearing the LocalStorage entry for User Settings after the first test runs is a . This ensures that the second test will be executed with default user settings. You can also consider actions like resetting the page to the default entry page and clearing cookies and database entries so that each new test begins with identical initial conditions.

For example, in Playwright, you can use the beforeAll(), beforeEach(), afterAll(), and afterEach() annotations to achieve it.

Check the next test suite with a couple of tests. The first is changing the user’s appearance settings, and the second is checking the default appearance.

TypeScript
test.describe('Test suite', () => {
  test('TC101 - update appearance settings to dark', async ({page}) => {
    await user.goTo(views.settings);
    await user.setAppearance(scheme.dark);
  });

  test('TC102 - check if default appearance is light', async ({page}) => {
    await user.goTo(views.settings);
    await user.checkAppearance(scheme.light);
  });
});

If such a test suite runs in parallel, everything will go smoothly. However, if these tests are run one by one, you could face a failed test because one of them is interfering with another. The first test changed the appearance from light to dark. The second test checks if the current appearance is light. But it will fail since the first test changed the default appearance already.

To solve such a problem, I’ve added the afterEach() hook executed after each test. In this case, it will navigate to the default view and clear the user’s appearance by removing it from the Local Storage.

TypeScript
test.afterEach(async ({ page }) => {
  await user.localStorage(appearanceSettings).clear()
  await user.goTo(views.home)
});

test.describe('Test suite', () => {
  test('TC101 - update appearance settings to dark', async ({page}) => {
    await user.goto(views.settings);
    await user.setAppearance(scheme.dark);
  });

  test('TC102 - check if default appearance is light', async ({page}) => {
    await user.goTo(views.settings);
    await user.checkAppearance(scheme.light);
  });
});

Using this approach, each test in this suite will be independent.

4. Use automatic retries wisely

No one is immune to encountering flaky tests, and a popular remedy for this issue involves using retries. You can configure retries to occur automatically, even at the CI/CD configuration level. For instance, you can set up automatic retries for each failed test, specifying a maximum retry count of three times. Any test that initially fails will be retried up to three times until the test execution cycle is complete. The advantage is that if your test is only slightly flaky, it may pass after a couple of retries.

However, the downside is that it could fail, resulting in extra runtime consumption. Moreover, this practice can inadvertently lead to an accumulation of flaky tests, as many tests may appear to “pass” on the second or third attempt, and you might incorrectly label them as stable. Therefore, I do not recommend setting auto-retries globally for your entire test project. Instead, use retries selectively in cases where you cannot promptly resolve the underlying issues in the test.

For instance, pretend you have a test case where you must upload some files using the ‘filechooser’ event. You get the <Timeout exceeded while waiting for event ‘filechooser’> error, which indicates that the Playwright test is waiting for a ‘filechooser’ event to occur but is taking longer ied timeout.

TypeScript
async function uploadFile(page: Page, file) {
  const fileChooserPromise = page.waitForEvent('filechooser');
  await clickOnElement(page, uploadButton);
  const fileChooser = await fileChooserPromise;
  await fileChooser.setFiles(file);
}

This can be due to various reasons, such as unexpected behavior in the application or a slow environment. Because it is random behavior, the first thing you could think about is to use automatic retry for this test. However, if you retry the entire test, you risk wasting extra time and getting the same behavior you got with the first error in the uploadFile() method itself, so if the error occurs, you don’t need to retry the entire test.

To do this, you can use the standard try…catch statement.

TypeScript
async function uploadFile(page: Page, file) {
  const maxRetries = 3;
  let retryCount = 0;

  while (retryCount < maxRetries) {
    try {
      const fileChooserPromise = page.waitForEvent('filechooser');
      await clickOnElement(page, uploadButton);
      const fileChooser = await fileChooserPromise;
      await fileChooser.setFiles(file);
      break; // Success, exit the loop
    } catch (error) {
      console.error(`Attempt ${retryCount + 1} failed: ${error}`);
      retryCount++;
    }
  }
}

Such an approach will save extra time and make your test less flaky.

In closing, the path to reliable automation tests is straightforward. You can minimize or actively address common challenges and implement intelligent strategies. Remember, this journey is ongoing, and your tests will become more dependable with persistence. Say goodbye to flakiness and welcome stability. Your commitment to quality ensures project success. Happy testing!

The post Four Horsemen of the Flaky Tests appeared first on SDEThub.com.

]]>
https://sdethub.com/eugene-truuts/four-horsemen-of-the-flaky-tests/feed/ 0 140
How To Get The Element OuterHTML In Playwright https://sdethub.com/eugene-truuts/how-to-get-the-element-outerhtml-in-playwright/ https://sdethub.com/eugene-truuts/how-to-get-the-element-outerhtml-in-playwright/#respond Fri, 08 Sep 2023 23:06:00 +0000 https://sdethub.com/?p=151 You may need to get the outerHTML of the element in your tests. This seems simple, but Playwright doesn’t have an outerHTML() function. Let’s start with an example. For this lesson, you must check if the popup window header has a font-sans property in its HTML code. First of all, let’s check the source code […]

The post How To Get The Element OuterHTML In Playwright appeared first on SDEThub.com.

]]>

You may need to get the outerHTML of the element in your tests. This seems simple, but Playwright doesn’t have an outerHTML() function.

Let’s start with an example.

For this lesson, you must check if the popup window header has a font-sans property in its HTML code.

First of all, let’s check the source code of such an element

HTML
<h2 class="contextual-sign-in-modal__context-screen-title
font-sans text-xl text-color-text my-2 mx-4 text-center"
    id="public_profile_contextual-sign-in-modal-header">
    Sign in to view Eugene’s full profile
</h2>

This seems a pretty simple task since this element already has the id attribute we can select using the XPath attribute selection:

TypeScript
const signInModalHeader = 
'//*[@id = "public_profile_contextual-sign-in-modal-header"]';

Feel free to check my How to master reliable XPath locators for your automated tests article, where you can find how to build reliable locators using XPath.

How to master reliable XPath locators for your automated tests

I’ve observed that the XPath locators mastering can challenge many individuals, including junior and mid-level…

truuts.medium.com

You can use the built-in Playwright .toHaveClass to achieve such a task. It ensures the Locator points to an element with given CSS classes. This needs to be a complete match or use a relaxed regular expression.

With this knowledge, you can easily build a test like that:

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

const signInModalHeader = '//*[@id = "public_profile_contextual-sign-in-modal-header"]';
const targetURL = 'https://www.linkedin.com/in/truuts/'

test('Check the outer HTML', async ({ page }) => {
    await page.goto(targetURL);
    await expect(page.locator(signInModalHeader), {
        message: 'Checking if sign-in modal header has font-sans property',
    }).toHaveClass(/font-sans/);
});

This test opens a LinkedIn profile page and applies the .toHaveClass Locator assertion to check if the element’s HTML has a “font-sans” in class.

It looks pretty simple.

The problem

But what if you use polling in your tests and want to use something like that?

TypeScript
await expect
    .poll(async () => page.locator(signInModalHeader).innerHTML(), {
      message: 'Checking if sign-in modal header has font-sans property',
      timeout: 5000,
    })
    .toHaveClass(/font-sans/);

You will get the error message that the Property toHaveClass does not exist on the type. The toHaveClass function needs to be recognized within the Playwright’s expect.poll chain. TypeScript is complaining because it doesn’t have information about the toHaveClass function within the context of expect.poll.

To solve this problem, you can use the toContain instead. This method ensures that the string value contains an expected substring. Comparison is case-sensitive.

Let’s try to rewrite our test

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

const signInModalHeader = '//*[@id = "public_profile_contextual-sign-in-modal-header"]';
const targetURL = 'https://www.linkedin.com/in/truuts/'

test('Check the outer HTML', async ({ page }) => {
  await page.goto(targetURL);
  await expect
      .poll(async () => page.locator(signInModalHeader), {
        message: 'Checking if sign-in modal header has font-sans property',
        timeout: 5000,
      })
      .toContain('font-sans');
});

If you run this test, you will receive an error

ShellScript
Error: Checking if sign-in modal header has font-sans property
Expected value: "font-sans"
Received object: {"_frame": {"_guid": "frame@99c2c4a43480ebe8a6f84fb53c31376b", 
"_type": "Frame"}, 
"_selector": "//*[@id = \"public_profile_contextual-sign-in-modal-header\"]"}

The Received object in this error message is an internal representation of a Playwright element located using the //*[@id = “public_profile_contextual-sign-in-modal-header”] XPath selector. We don’t need the element representation; we need our element’s HTML to try adding the .innerHTML, which returns the element.innerHTML.

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

const signInModalHeader = '//*[@id = "public_profile_contextual-sign-in-modal-header"]';
const targetURL = 'https://www.linkedin.com/in/truuts/'

test('Check the outer HTML', async ({ page }) => {
  await page.goto(targetURL);
  await expect
      .poll(async () => page.locator(signInModalHeader).innerHTML(), {
        message: 'Checking if sign-in modal header has font-sans property',
        timeout: 5000,
      })
      .toContain('font-sans');
});

Now you’ll get a different error message.

ShellScript
Error: Checking if sign-in modal header has font-sans property

Expected substring: "font-sans"
Received string:    "
                Sign in to view Eugene’s full profile
              "

This happens because you’re trying to get the inner HTML of the element by its id Attribute locator.

HTML
<h2 class="contextual-sign-in-modal__context-screen-title 
font-sans text-xl text-color-text my-2 mx-4 text-center"
    id="public_profile_contextual-sign-in-modal-header">
    Sign in to view Eugene’s full profile
</h2>

Since the id Attribute is an outside part of the element, you’re getting its inner part indeed. In this case, the “Sign in to view Eugene’s full profile” text is the inner part of the element. We could try checking the outerHTML() instead, and we have trouble here because Playwright doesn’t have it.

To solve it and the element’s outerHTML in Playwright, you can use the evaluate() method to call the .outerHTML() directly on the element in the DOM.

I’ve just implemented a simple method for obtaining the outer HTML content of a specified element on a web page using Playwright. It handles the element locating and evaluation steps, retrieving HTML content.

TypeScript
await page.locator(element).evaluate((el) => el.outerHTML);

All you have to do now is to call this method inside your polling expectation.

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

const signInModalHeader = '//*[@id = "public_profile_contextual-sign-in-modal-header"]';
const targetURL = 'https://www.linkedin.com/in/truuts/'

test('Check the outer HTML', async ({ page }) => {
    await page.goto(targetURL);

    const element = page.locator(signInModalHeader);
    await expect.poll(async () => {
        return await element.evaluate((el) => el.outerHTML);
    }, {
        message: 'Checking if sign-in modal header has font-sans property',
        timeout: 5000,
    }).toContain('font-sans');
});

Now our test has passed, and we can just a little refactor it to make it easy to understand:

TypeScript
import {expect, Page, test} from '@playwright/test';

const signInModalHeader = '//*[@id = "public_profile_contextual-sign-in-modal-header"]';
const targetURL = 'https://www.linkedin.com/in/truuts/'
const TIMEOUT = 5000;

async function checkOuterHtmlText(page: Page, elementLocator, expectedText) {
    await expect.poll(async () => {
        return await page.locator(elementLocator).evaluate((el) => el.outerHTML);
    }, {
        message: `Checking if ${elementLocator} outer HTML has a ${expectedText} property during the ${TIMEOUT}ms.`,
        timeout: TIMEOUT,
    }).toContain(expectedText);
}

test('Check the outer HTML', async ({ page }) => {
    await page.goto(targetURL);
    await checkOuterHtmlText(page, signInModalHeader, 'font-sans');
});

This test verifies that the outer HTML content of the element specified by signInModalHeader contains the text ‘font-sans’ within a 5-second timeout period. If the text is found within the element’s outer HTML, the test passes; otherwise, it fails with the error message:

ShellScript
Error: Checking if //*[@id = "public_profile_contextual-sign-in-modal-header"]
outer HTML has a font-sans property during the 5000ms.

The post How To Get The Element OuterHTML In Playwright appeared first on SDEThub.com.

]]>
https://sdethub.com/eugene-truuts/how-to-get-the-element-outerhtml-in-playwright/feed/ 0 151