Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* The MIT License
*
* Copyright 2023 Karate Labs Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.intuit.karate.playwright.driver;
import com.intuit.karate.core.*;
import com.intuit.karate.driver.Mouse;
import com.intuit.karate.driver.*;
import com.intuit.karate.graal.JsValue;
import com.intuit.karate.http.HttpRequest;
import com.intuit.karate.http.ResourceType;
import com.intuit.karate.http.Response;
import com.microsoft.playwright.Frame;
import com.microsoft.playwright.*;
import com.microsoft.playwright.options.*;
import org.graalvm.polyglot.Value;
import java.io.Closeable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
/**
* Implementation of a Karate Driver for Playwright.
*
* Unlike the original PlaywrightDriver living in karate-core which was based on
* the internal wire protocol, this one uses the public Playwright APIs. To use
* it, make sure the karate-playwright dependency is added in your pom.xml, and
* located before karate-core.
*
* It supports: - xpath, css, wildcard locators as well as friendly locators
* (through custom locators which we believe are better suited to Karate than
* the 'right-of' pseudo selectors offered by PW) - headless tests - http
* requests intercepting (basic support, only urlPatterns are matched) - and of
* course all the browsers supported by Playwright.
*
* This driver will start up a Playwright engine unless the playwrightUrl is
* specified, in which case the driver will try to connect to that url.
*
* A couple of additional options may be specified in the playwrightOptions
* property: - installBrowsers ("true"/"false"): whether PW will automatically
* download and install the browsers - channel (e.g. "chrome"): for the
* "chromium" browserType, Playwright allows us to pick the underlying engine.
*
* The following points are not 100% identical to the other Drivers - Cookies
* are supported but a domain/path or url key is mandatory - Retries are
* supported but, per doc, drivers should wait the specified
*
interval
number of milliseconds before retrying. This driver will
* however wait at most
interval
milliseconds, but it leverages
* Playwright's auto-wait/auto-retry to return as soon as the element is
* available and won't wait for the full specified interval.But ig(count: 3,
* interval: 3000 milliseconds) means try three times, and wait for 3 seconds
* before the next re-try attempt. In fact, if slowDiv takes 2 seconds to load,
* retry(3, 1500).click('#slowDiv') will return in roughly 2s. So will retry(2,
* 2000), and retry(5, 800). Of course, retry(3, 500) will fail.
*/
/*
* Possible improvements:
* - add option to enable tracing
* - take advantage of PW's multi browser capability.
*
*/
public class PlaywrightDriver implements Driver {
// Revert back to options.timeout
private static final Integer DEFAULT_TIMEOUT = null;
private static final String FRIENDLY_ENGINE = "{\n"
+ " queryAll(root,args) {\n"
+ " function retain_right(rootRect, itemRect) {\n"
+ " return itemRect.x >= (rootRect.x + rootRect.width) && itemRect.y <= (rootRect.y + rootRect.height) && (itemRect.y + itemRect.height) >=rootRect.y;\n"
+ " }\n"
+ " function retain_left(rootRect, itemRect) {\n"
+ " return (itemRect.x + itemRect.width) <= rootRect.x && itemRect.y <= (rootRect.y + rootRect.height) && (itemRect.y + itemRect.height) >=rootRect.y;\n"
+ " }\n"
+ " function retain_below(rootRect, itemRect) {\n"
+ " return itemRect.y >= (rootRect.y + rootRect.height) && itemRect.x <= (rootRect.x + rootRect.width) && (itemRect.x + itemRect.width) >=rootRect.x;\n"
+ " }\n"
+ " function retain_above(rootRect, itemRect) {\n"
+ " return (itemRect.y + itemRect.height) <= rootRect.y && itemRect.x <= (rootRect.x + rootRect.width) && (itemRect.x + itemRect.width) >=rootRect.x;\n"
+ " }\n"
+ " function retain_near(rootRect, itemRect) {\n"
+ " return true;\n"
+ " }\n"
+ " function items_list(selector) {\n"
+ " if (selector.startsWith('/') || selector.startsWith('xpath=')) {\n"
+ " let items_list = [];\n"
+ " let query = document.evaluate(argsParts[1],document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);\n"
+ " for (let i = 0; i a[1] - b[1]).map(item => item[0]);\n"
+ " }\n"
+ "}";
final PlaywrightDriverOptions options;
private final Playwright playwright;
private final Browser browser;
private final BrowserContext browserContext;
Page page;
private FrameTrait root;
private boolean terminated = false;
private String dialogText;
double retryTimeout;
public interface PlaywrightDriverFactory {
T create(PlaywrightDriverOptions options, Browser browser, Playwright playwright);
}
public static PlaywrightDriver start(Map map, ScenarioRuntime sr) {
return start(map, sr, PlaywrightDriver::new);
}
public static T start(Map map, ScenarioRuntime sr, PlaywrightDriverFactory factory) {
PlaywrightDriverOptions options = new PlaywrightDriverOptions(map, sr, 4444, "playwright");
Map pwOptions = options.playwrightOptions == null ? Collections.emptyMap() : options.playwrightOptions;
String browserTypeOption = (String) pwOptions.getOrDefault("browserType", "chromium");
Browser browser;
if (Boolean.valueOf(pwOptions.getOrDefault("installBrowsers", true) == Boolean.FALSE)) {
options.driverLogger.debug("Playwright browsers will not be installed.");
// ensureDriverInstalled is called by Playwright.create, but the installBrowsers is forced to true.
// We call it here with a falsy installBrowsers, the singleton will be created and subsequent call from Playwright.create will have no effect
// Disabling auto install might be useful when behind a firewall. playwrightOptions.channel might then need to be specified for Playwright to run the locally installed browser, else it will complain.
com.microsoft.playwright.impl.driver.Driver.ensureDriverInstalled(Collections.emptyMap(), false);
} else {
// Will actually be installed by the Playwright.create call below
options.driverLogger.info("Installing Playwright browsers (this may take some time)...");
}
try {
Playwright playwright = Playwright.create();
playwright.selectors().register("friendly", FRIENDLY_ENGINE);
Method browserTypeMethod = Playwright.class.getDeclaredMethod(browserTypeOption);
BrowserType browserType = (BrowserType) browserTypeMethod.invoke(playwright);
if (options.start) {
browser = browserType.launch(new BrowserType.LaunchOptions()
.setHeadless(options.headless)
.setChannel((String) pwOptions.getOrDefault("channel", "chromium")));
} else {
String playwrightUrl = options.playwrightUrl;
if (playwrightUrl == null) {
throw new RuntimeException("playwrightUrl is mandatory if start == false");
}
browser = browserType.connect(playwrightUrl);
}
return factory.create(options, browser, playwright);
} catch (NoSuchMethodException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
throw new RuntimeException(e);
}
}
public PlaywrightDriver(PlaywrightDriverOptions options, Browser browser, Playwright playwright) {
this.options = options;
this.playwright = playwright;
options.setDriver(this);
this.browser = browser;
this.browserContext = browser.newContext();
this.timeout(null);
setPage(browserContext.newPage());
}
private void setPage(Page page) {
this.page = page;
this.root = FrameTrait.of(page);
}
@Override
public void quit() {
if (!terminated) {
terminated = true;
browserContext.close();
browser.close();
playwright.close();
}
}
@Override
public String getDialogText() {
return dialogText;
}
// private Locator locator(String locator) {
// return this.locator.locatorFor(locator);
// }
@Override
public void dialog(boolean accept) {
dialog(accept, Dialog::accept);
}
@Override
public void dialog(boolean accept, String input) {
dialog(accept, dialog -> dialog.accept(input));
}
private void dialog(boolean accept, Consumer