结合common-pools 自定义创建Playwright池

结合common-pools 自定义创建Playwright池

目录

简介

Playwright是微软开源的一个UI自动化测试工具。借助额外的语言支持以及跨现代浏览器引擎Chromium,Firefox和WebKit执行的能力,这使Playwright与Selenium WebDriver处于同一类别,成为所有需要交叉测试的Web测试人员(不仅是JS)的可行测试解决方案浏览器测试功能,适用于复杂的应用程序。

产生的问题

在工作中,我们使用到Playwright,每一次用到都需要初始化,打开页面、关闭资源,对于系统来说都是一种消耗

解决方案

我结合Apache提供的对象池技术,用Playwright预先创造出来几个页面,做了一个池管理

实现方式如下

BrowserContextPooledObjectFactory

package xxx.playwright;

import com.microsoft.playwright.*;
import org.apache.commons.pool2.DestroyMode;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.PooledObjectFactory;
import org.apache.commons.pool2.impl.DefaultPooledObject;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author lishanbiao
 * @date 2021/7/8
 */
public class BrowserContextPooledObjectFactory implements PooledObjectFactory<BrowserContext> {
		// playwright管理容器
    private static final Map<BrowserContext, Playwright> PLAYWRIGHT_MAP = new ConcurrentHashMap<>();
 		// 激活池中物(playwright),借出之前做的操作,相当于刷新实体
    @Override
    public void activateObject(PooledObject<BrowserContext> p) throws Exception {
        p.getObject().clearCookies();
    }
  
  	// 销毁池中物(playwright)
    @Override
    public void destroyObject(PooledObject<BrowserContext> p) throws Exception {
        BrowserContext browserContext = p.getObject();
        Playwright playwright = PLAYWRIGHT_MAP.remove(browserContext);
        if (playwright != null) {
            playwright.close();
        }
    }
		// 创建池中物(playwright)
    @Override
    public PooledObject<BrowserContext> makeObject() throws Exception {
        Playwright playwright = Playwright.create();
        BrowserContext browserContext = playwright.chromium()
                .launch(new BrowserType.LaunchOptions()
                   //  .setHeadless(false)
                ).newContext(new Browser.NewContextOptions()
                        .setScreenSize(1920, 1080));
        browserContext.newPage();
        PLAYWRIGHT_MAP.put(browserContext, playwright);
        return new DefaultPooledObject<>(browserContext);
    }
		// 归还一个池中物(playwright)时调用,不应该activateObject冲突
    @Override
    public void passivateObject(PooledObject<BrowserContext> p) throws Exception {
        p.getObject().pages().get(0).evaluate("try {window.localStorage.clear()} catch(e){console.log(e)}");
        p.getObject().clearCookies();
        p.getObject().pages().get(0).navigate("about:blank");
    }
  
		// 检测对象是否"有效";Pool中不能保存无效的"对象",因此"后台检测线程"会周期性的检测Pool中"对象"的有效性,如果对象无效则会导致此对象从Pool中移除,并destroy;此外在调用者从Pool获取一个"对象"时,也会检测"对象"的有效性,确保不能讲"无效"的对象输出给调用者;当调用者使用完毕将"对象归还"到Pool时,仍然会检测对象的有效性.所谓有效性,就是此"对象"的状态是否符合预期,是否可以对调用者直接使用;如果对象是Socket,那么它的有效性就是socket的通道是否畅通/阻塞是否超时等.
  	// 这里若要检测,需要在PoolConfig中配置检测项目。
  	// true:检测正常,符合预期;false:异常,销毁对象
    @Override
    public boolean validateObject(PooledObject<BrowserContext> p) {
        return true;
    }
}

PlaywrightUtil

package xxx.playwright;

import cn.com.rmxc.java.commons.json.JsonUtils;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.microsoft.playwright.*;
import com.microsoft.playwright.options.Cookie;
import com.microsoft.playwright.options.MouseButton;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.stereotype.Component;

import javax.annotation.PreDestroy;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author lishanbiao
 * @date 2021/7/8
 */
@Slf4j
public class PlaywrightUtil {

    private static final GenericObjectPool<BrowserContext> OBJECT_POOL;
    private static final Map<String, Page> PAGE_MAP = new HashMap<>();
    private static final String TOKEN_SPLITTER = ";";

    static {
        GenericObjectPoolConfig<BrowserContext> config = new GenericObjectPoolConfig<>();
        config.setMinIdle(4);
        config.setMaxIdle(4);
        OBJECT_POOL = new GenericObjectPool<>(new BrowserContextPooledObjectFactory(), config);
    }

    /**
     * 获取Page对象
     *
     * @param id
     * @return
     * @throws Exception
     */
    public static Page borrowPage(String id) throws Exception {
        Page page = getPage(id);
        if (page == null) {
            page = OBJECT_POOL.borrowObject().pages().get(0);
            putPage(id, page);
        }
        return page;
    }

    /**
     * 归还page
     *
     * @param id
     */
    public static void returnPage(String id) {
        Page page = removePage(id);
        if (page != null) {
            OBJECT_POOL.returnObject(page.context());
        }
    }

    /**
     * 获取cookie
     *
     * @param page
     * @return
     */
    public static String getCookies(Page page) {
        return cookieToString(page.context().cookies());
    }

    /**
     * 获取cookie
     *
     * @param cookies
     * @return
     */
    public static String cookieToString(List<Cookie> cookies) {
        return cookies.stream().map(cookie -> cookie.name + "=" + cookie.value).collect(Collectors.joining(TOKEN_SPLITTER));
    }

    /**
     * 退出playwright
     */
    public static void exitPlaywright() {
        OBJECT_POOL.close();
    }

    /**
     * 存放page
     *
     * @param key
     * @param page
     */
    private static void putPage(String key, Page page) {
        PAGE_MAP.put(key, page);
    }

    /**
     * 获取page
     *
     * @param key
     * @return
     */
    private static Page getPage(String key) {
        return PAGE_MAP.get(key);
    }

    /**
     * 删除page
     *
     * @param key
     */
    private static Page removePage(String key) {
        return PAGE_MAP.remove(key);
    }

    /**
     * 图片日志
     *
     * @param id
     */
    public static void pictureLog(String id) {
        Page page = getPage(id);
        if (page != null) {
            pictureLog(id, page);
        } else {
            log.error("Playwright截图未找到page对象, id:{}", id);
        }
    }

    /**
     * 图片日志
     *
     * @param id
     * @param page
     */
    public static void pictureLog(String id, Page page) {
        try {
            Path path = Files.createTempFile("playwright-log-", ".jpg");
            page.screenshot(new Page.ScreenshotOptions().setFullPage(true).setPath(path));
            log.info("截图完成,path:{}", path.toAbsolutePath());
        } catch (Throwable t) {
            log.error("截图失败", t);
        }
    }

    /**
     * 清空localStorage
     *
     * @param page
     */
    public static void clearLocalStorage(Page page) {
        page.evaluate("window.localStorage.clear();");
    }

    /**
     * 滑动滑块
     *
     * @param page
     * @param slideElementPath
     * @param slideLength
     * @param steps
     */
    public static void slide(Page page, String slideElementPath, int slideLength, int steps) {
        slide(page, page.waitForSelector(slideElementPath, new Page.WaitForSelectorOptions().setTimeout(TimeUnit.SECONDS.toMillis(5))), slideLength, steps);
    }

    /**
     * 滑动滑块
     *
     * @param page
     * @param elementHandle
     * @param slideLength
     * @param steps
     */
    public static void slide(Page page, ElementHandle elementHandle, int slideLength, int steps) {
        Mouse mouse = page.mouse();
        mouse.move(elementHandle.boundingBox().x, elementHandle.boundingBox().y);
        mouse.down(new Mouse.DownOptions().setButton(MouseButton.LEFT));
        mouse.move(elementHandle.boundingBox().x + slideLength, elementHandle.boundingBox().y, new Mouse.MoveOptions().setSteps(steps));
        mouse.up();
    }

    /**
     * 解析token
     *
     * @param token
     * @param key
     * @return
     */
    public static String parseToken(String token, String key) {
        for (String s : token.split(TOKEN_SPLITTER)) {
            String tokenKey = s.substring(0, s.indexOf("="));
            if (tokenKey.equals(key)) {
                return s.substring(s.indexOf("=") + 1, s.length() - 1);
            }
        }
        return "";
    }

    public static List<Cookie> token2Cookie(String token) {
        List<String> list = JsonUtils.fromJson(token, new TypeReference<List<String>>() {});
        List<com.microsoft.playwright.options.Cookie> cookieList = new ArrayList<>();
        list.forEach(cookie1 -> {
            OldToken oldToken = JsonUtils.fromJson(cookie1, OldToken.class);
            Cookie cookie2 = new Cookie(oldToken.getName(), oldToken.getValue());
            cookieList.add(cookie2);
        });
        return cookieList;
    }

    @Component
    public static class TerminateHandler {
        /**
         * 手动关闭playwright
         */
        @PreDestroy
        public void preDestroy() {
            PlaywrightUtil.exitPlaywright();
        }

    }

    /**
     * 解析cookie转json,后续作废
     *
     * @param token
     * @return
     */
    public static String parseCookieToJson(String token) {
        String[] tokenArray = token.split(";");
        List<String> tokens = new ArrayList<>();
        for (String s : tokenArray) {
            String name = s.substring(0, s.indexOf('='));
            String value = s.substring(s.indexOf('=') + 1);
            tokens.add(JsonUtils.toJson(new OldToken(name, value)));
        }
        return JsonUtils.toJson(tokens);
    }

    /**
     * 解析json转cookies
     *
     * @param token
     * @return
     */
    public static List<Cookie> parseJsonToCookie(String token) {
        List<Cookie> cookies = new ArrayList<>();
        JsonNode jsonNode = JsonUtils.readTree(token);
        for (JsonNode node : jsonNode) {
            Cookie cookie = new Cookie(node.get("name").asText(), node.get("value").asText());
            cookie.setPath(node.get("path").asText());
            if(node.hasNonNull("url")){
                cookie.setUrl(node.get("url").asText());
            }
            if(node.hasNonNull("domain")){
                cookie.setDomain(node.get("domain").asText());
            }
            cookie.setExpires(node.get("expires").asDouble());
            cookie.setSecure(node.get("secure").asBoolean());
            cookies.add(cookie);
        }
        return cookies;
    }
}
上一篇:java作业斗地主实现


下一篇:Spring MVC 解析JSON入参意外的丢失参数