Skip to content

Commit

Permalink
[plugin-web-app-playwright] Add frame switching steps (#5141)
Browse files Browse the repository at this point in the history
  • Loading branch information
avinBar authored Jun 19, 2024
1 parent 8f629f8 commit a3cf626
Show file tree
Hide file tree
Showing 9 changed files with 318 additions and 41 deletions.
38 changes: 38 additions & 0 deletions docs/modules/plugins/pages/plugin-web-app-playwright.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -306,3 +306,41 @@ Given I am on page with URL `https://vividus-test-site-a92k.onrender.com/`
When I execute javascript `JSON.stringify(window.performance.timing)` and save result to scenario variable `timings`
Then number of JSON elements from `${timings}` by JSON path `$.connectStart` is = 1
----

=== Context steps
==== Switch to the default context of page

Switches context to the root https://developer.mozilla.org/en-US/docs/Web/HTML/Element/html[<html>] element of the current page.

[source,gherkin]
----
When I switch back to page
----

.Switch to user context and back to the default context of page
----
Given I am on page with URL `https://vividus-test-site-a92k.onrender.com/elementState.html`
When I change context to element located by `id(button-hide)`
Then text `Element to hide` does not exist
When I switch back to page
Then text `Element to hide` exists
----

==== Switch context to a frame

Switches to https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe[<iframe>] or https://developer.mozilla.org/en-US/docs/Web/HTML/Element/frame[<frame>] element using one of the supported locators.

[source,gherkin]
----
When I switch to frame located by `$locator`
----

* `$locator` - <<_locator>> of frame element.

.Switch to frame
----
Given I am on page with URL `https://vividus-test-site-a92k.onrender.com/nestedFrames.html`
Then text `Modal Frame Example` does not exist
When I switch to frame located by `id(parent)`
Then text `Modal Frame Example` exists
----
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,12 @@

package org.vividus.ui.web.playwright;

import java.util.Deque;
import java.util.LinkedList;
import java.util.Optional;
import java.util.function.Function;

import com.microsoft.playwright.FrameLocator;
import com.microsoft.playwright.Locator;
import com.microsoft.playwright.Page;

Expand All @@ -45,12 +48,37 @@ public Page getCurrentPage()
return getPlaywrightContext().page;
}

public void setCurrentFrame(FrameLocator frame)
{
getPlaywrightContext().frames.add(frame);
}

public FrameLocator getCurrentFrame()
{
return getPlaywrightContext().frames.isEmpty() ? null : getPlaywrightContext().frames.getLast();
}

public void setContext(Locator context)
{
getPlaywrightContext().context = context;
}

public void reset()
{
getPlaywrightContext().frames.clear();
resetContext();
}

public void resetToActiveFrame()
{
Deque<FrameLocator> frames = getPlaywrightContext().frames;
while (!frames.isEmpty() && !frames.getLast().owner().isVisible())
{
frames.removeLast();
}
}

public void resetContext()
{
getPlaywrightContext().context = null;
}
Expand All @@ -59,22 +87,25 @@ public Locator locateElement(PlaywrightLocator playwrightLocator)
{
String locator = playwrightLocator.getLocator();
Locator locatorInContext = getInCurrentContext(context -> context.locator(locator),
page -> page.locator(locator));
page -> page.locator(locator), frame -> frame.locator(locator));
return (playwrightLocator.getVisibility() == Visibility.VISIBLE)
? locatorInContext.locator("visible=true") : locatorInContext;
}

public Locator getCurrentContexOrPageRoot()
{
return getInCurrentContext(context -> context, page -> page.locator("//html/body"));
return getInCurrentContext(context -> context, page -> page.locator("//html/body"), FrameLocator::owner);
}

private <R> R getInCurrentContext(Function<Locator, R> elementContextAction, Function<Page, R> pageContextAction)
private <R> R getInCurrentContext(Function<Locator, R> elementContextAction, Function<Page, R> pageContextAction,
Function<FrameLocator, R> frameContextAction)
{
PlaywrightContext playwrightContext = getPlaywrightContext();
return Optional.ofNullable(playwrightContext.context)
.map(elementContextAction)
.orElseGet(() -> pageContextAction.apply(playwrightContext.page));
.orElseGet(() -> Optional.ofNullable(getCurrentFrame())
.map(frameContextAction)
.orElseGet(() -> pageContextAction.apply(playwrightContext.page)));
}

private PlaywrightContext getPlaywrightContext()
Expand All @@ -86,5 +117,6 @@ private static final class PlaywrightContext
{
private Page page;
private Locator context;
private final LinkedList<FrameLocator> frames = new LinkedList<>();
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2019-2023 the original author or authors.
* Copyright 2019-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -48,7 +48,11 @@ public MouseSteps(UiContext uiContext, ISoftAssert softAssert)
@When("I click on element located by `$locator`")
public void clickOnElement(PlaywrightLocator locator)
{
runMouseAction(locator, Locator::click, "click");
Consumer<Locator> clickAction = element -> {
element.click();
afterClick();
};
runMouseAction(locator, clickAction, "click");
}

/**
Expand Down Expand Up @@ -85,4 +89,10 @@ private void runMouseAction(PlaywrightLocator locator, Consumer<Locator> mouseAc
softAssert.recordFailedAssertion("The element to " + actionDescription + " is not found", timeoutError);
}
}

private void afterClick()
{
uiContext.getCurrentPage().waitForLoadState();
uiContext.resetToActiveFrame();
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2019-2023 the original author or authors.
* Copyright 2019-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -65,7 +65,6 @@ public void openMainApplicationPage()
@Given("I am on page with URL `$pageUrl`")
public void openPage(String pageUrl)
{
uiContext.reset();
Optional.ofNullable(uiContext.getCurrentPage()).orElseGet(this::openNewTab).navigate(pageUrl);

Check warning on line 68 in vividus-plugin-web-app-playwright/src/main/java/org/vividus/ui/web/playwright/steps/PageSteps.java

View workflow job for this annotation

GitHub Actions / qodana

AutoCloseable used without 'try'-with-resources

'Page' used without 'try'-with-resources statement
}

Expand All @@ -75,7 +74,6 @@ public void openPage(String pageUrl)
@When("I refresh page")
public void refreshPage()
{
uiContext.reset();
uiContext.getCurrentPage().reload();
}

Expand All @@ -85,7 +83,6 @@ public void refreshPage()
@When("I navigate back")
public void navigateBack()
{
uiContext.reset();
uiContext.getCurrentPage().goBack();
}

Expand Down Expand Up @@ -149,8 +146,15 @@ public void assertPageTitle(StringComparisonRule comparisonRule, String text)

private Page openNewTab()
{
uiContext.reset();
Page page = browserContextProvider.get().newPage();
uiContext.setCurrentPage(page);
page.onFrameNavigated(frame -> {
if (frame.parentFrame() == null)
{
uiContext.reset();
}
});
return page;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2019-2023 the original author or authors.
* Copyright 2019-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -16,6 +16,9 @@

package org.vividus.ui.web.playwright.steps;

import java.util.Optional;

import com.microsoft.playwright.FrameLocator;
import com.microsoft.playwright.Locator;
import com.microsoft.playwright.assertions.PlaywrightAssertions;

Expand Down Expand Up @@ -45,7 +48,7 @@ public SetContextSteps(UiContext uiContext, PlaywrightSoftAssert playwrightSoftA
@When("I reset context")
public void resetContext()
{
uiContext.reset();
uiContext.resetContext();
}

/**
Expand Down Expand Up @@ -75,4 +78,58 @@ public void changeContextInScopeOfCurrentContext(PlaywrightLocator locator)
LOGGER.info("The context is successfully changed");
});
}

/**
* Switches to a frame found by locator.
* <p>
* A <b>frame</b> is used for splitting browser page into several segments, each of which can show a different
* document (content). This enables updates of parts of a website while the user browses without making them reload
* the whole page (this is now largely replaced by AJAX).
* <p>
* <b>Frame</b> elements are specified by {@code <iframe>} tag as the following example shows:
* <pre>
* {@code <iframe attributeType=}<b>'attributeValue'</b>{@code > some iframe content</iframe>}
* </pre>
* <p>
* Actions performed at this step:
* <ul>
* <li>Resets the context;
* <li>Finds a frame using specified locator;
* <li>If the frame is found, performs switch to it.
* </ul>
* @see <a href="https://en.wikipedia.org/wiki/HTML_element#Frames"><i>Frames</i></a>
* @see <a href="https://www.w3schools.com/tags/default.asp"><i>HTML Element Reference</i></a>
* @param locator The locator to locate frame element
*/
@When("I switch to frame located by `$locator`")
public void switchToFrame(PlaywrightLocator locator)
{
resetContext();
FrameLocator frameLocator = getFrameLocator(locator.getLocator());
playwrightSoftAssert.runAssertion("The frame to switch is not found", () -> {
PlaywrightAssertions.assertThat(frameLocator.locator(":root")).hasCount(1);
uiContext.setCurrentFrame(frameLocator);
LOGGER.info("Successfully switched to frame");
});
}

/**
* Switching to the default content of the page
* <p>
* Actions performed at this step:
* <ul>
* <li>Switches focus to the root tag of the page, this is as a rule {@code <html>} tag.
* </ul>
*/
@When("I switch back to page")
public void switchingToDefault()
{
uiContext.reset();
}

private FrameLocator getFrameLocator(String locator)
{
return Optional.ofNullable(uiContext.getCurrentFrame()).map(frame -> frame.frameLocator(locator))
.orElseGet(() -> uiContext.getCurrentPage().frameLocator(locator));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@

package org.vividus.ui.web.playwright;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;

import com.microsoft.playwright.FrameLocator;
import com.microsoft.playwright.Locator;
import com.microsoft.playwright.Page;

Expand Down Expand Up @@ -63,6 +65,22 @@ void shouldReturnNullWhenPageNotSet()
assertNull(currentPage);
}

@Test
void shouldSetAndGetFrame()
{
FrameLocator frame = mock();
uiContext.setCurrentFrame(frame);
var currentFrame = uiContext.getCurrentFrame();
assertSame(frame, currentFrame);
}

@Test
void shouldReturnNullWhenFrameNotSet()
{
var currentFrame = uiContext.getCurrentFrame();
assertNull(currentFrame);
}

@Test
void shouldLocateElementOnPage()
{
Expand Down Expand Up @@ -125,6 +143,17 @@ void shouldReturnPageRootWhenNoContextIsSet()
assertSame(root, actual);
}

@Test
void shouldReturnFrameWhenNoContextIsSet()
{
FrameLocator frame = mock();
uiContext.setCurrentFrame(frame);
Locator root = mock();
when(frame.owner()).thenReturn(root);
Locator actual = uiContext.getCurrentContexOrPageRoot();
assertSame(root, actual);
}

@Test
void shouldLocateVisibleElement()
{
Expand All @@ -138,4 +167,37 @@ void shouldLocateVisibleElement()
Locator actual = uiContext.locateElement(playwrightLocator);
assertSame(visibleLocator, actual);
}

@Test
void shouldResetFrame()
{
setupFrameMock(false);
uiContext.resetToActiveFrame();
assertNull(uiContext.getCurrentFrame());
}

@Test
void shouldNotResetFrameWhenFrameIsVisible()
{
FrameLocator frame = setupFrameMock(true);
uiContext.resetToActiveFrame();
assertEquals(frame, uiContext.getCurrentFrame());
}

@Test
void shouldNotThrowExceptionWhenResetFrameWithNoFrames()
{
uiContext.resetToActiveFrame();
assertNull(uiContext.getCurrentFrame());
}

private FrameLocator setupFrameMock(boolean visibility)
{
FrameLocator frame = mock();
Locator frameOwner = mock();
uiContext.setCurrentFrame(frame);
when(frame.owner()).thenReturn(frameOwner);
when(frameOwner.isVisible()).thenReturn(visibility);
return frame;
}
}
Loading

0 comments on commit a3cf626

Please sign in to comment.