Skip to content

Commit

Permalink
Merge pull request #141 from yorkxin/selenium-firefox
Browse files Browse the repository at this point in the history
E2E test on Firefox
  • Loading branch information
yorkxin committed Apr 30, 2024
2 parents d178148 + 374a520 commit 002f3cf
Show file tree
Hide file tree
Showing 27 changed files with 1,154 additions and 405 deletions.
14 changes: 8 additions & 6 deletions e2e/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@

This folder contains E2E test cases of Copy as Markdown extension.

It uses [Selenium](https://www.selenium.dev/) to automate the browsers and assert the contents of the clipboard.
It uses [Selenium](https://www.selenium.dev/) and [java.awt.Robot](https://docs.oracle.com/javase%2F7%2Fdocs%2Fapi%2F%2F/java/awt/Robot.html) to automate the browsers and assert the contents of the clipboard.

## System Requirements

* Java
* Maven
* Google Chrome
* Firefox

## Development

Expand All @@ -22,7 +23,7 @@ It uses [Selenium](https://www.selenium.dev/) to automate the browsers and asser

## Setup

### macOS Assistive Control
### macOS Accessibility Warnings

When you first run the test cases in the terminal, you will be prompted about
the permissions of Assistive Control.
Expand All @@ -41,17 +42,18 @@ Please hold back and don't touch mouse / keyboard while the tests are running.

### JetBrains Aqua

Just right-click on the test folder and choose "Run Tests".
- Right-click on `testng.xml` and choose "Run Tests".
- To run test cases for a particular browser, select `testng-<browser>.xml`.

## Architecture

* `src/test` contains all the test scripts.
* `support/e2e-test-extension` is a Web Extension used to control tabs in ways that Selenium can't do,
* `support/e2e-test-extension*` are Web Extensions used to control tabs in ways that Selenium can't do,
such as tab grouping, tab highlighting etc.
* `support/pages` contains static fixture pages used in test cases. When test suite starts, it will run a static server
listening at `localhost:5566`. The E2E Test Extension will open those pages.

## TODO

* Test on Firefox
* Test on Linux
* Test Edge
* Test on Linux & Windows
21 changes: 10 additions & 11 deletions e2e/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.target>21</maven.compiler.target>
<maven.compiler.source>21</maven.compiler.source>
<junit.version>5.10.0</junit.version>
<aspectj.version>1.9.19</aspectj.version>
<allure.version>2.24.0</allure.version>
</properties>
Expand All @@ -27,20 +26,14 @@
</dependency>
<dependency>
<groupId>io.qameta.allure</groupId>
<artifactId>allure-junit5</artifactId>
<artifactId>allure-testng</artifactId>
<version>${allure.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit.version}</version>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>7.7.1</version>
<scope>test</scope>
</dependency>
<dependency>
Expand All @@ -61,6 +54,12 @@
<version>3.2.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>7.7.1</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
Expand Down
156 changes: 121 additions & 35 deletions e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/BaseTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@

import com.sun.net.httpserver.HttpServer;
import io.github.sukgu.Shadow;
import org.apache.commons.io.FileUtils;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.TestInstance;
import org.openqa.selenium.*;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.edge.EdgeDriver;
import org.openqa.selenium.edge.EdgeOptions;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.firefox.FirefoxOptions;
import org.openqa.selenium.firefox.FirefoxProfile;
import org.openqa.selenium.interactions.Actions;
import org.testng.annotations.*;

import java.awt.*;
import java.awt.datatransfer.Clipboard;
Expand All @@ -23,32 +24,29 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static com.sun.net.httpserver.SimpleFileServer.createFileHandler;

record Window(String handle, String url, String title) {}

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class BaseTest {
protected String browser;
protected WebDriver driver;
private HttpServer server;
protected String extId;
protected String e2eExtId;
protected String mainWindowHandle;
protected String demoWindowHandle;
protected Clipboard clipboard;
List<Window> windows = new ArrayList<>();

@BeforeAll
public void setUp() throws IOException, InterruptedException, AWTException {
ChromeOptions options = new ChromeOptions();
// Fix the issue https://github.com/SeleniumHQ/selenium/issues/11750
options.addArguments("--remote-allow-origins=*");
options.addArguments("--load-extension=../chrome,./support/e2e-test-extension");
driver = new ChromeDriver(options);
driver.manage().window().maximize();
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));

protected final static String BROWSER_CHROME = "chrome";
protected final static String BROWSER_FIREFOX = "firefox";

@Parameters("browser")
@BeforeClass
public void setUp(@Optional(BROWSER_CHROME) String browserName) throws IOException {
browser = browserName;
driver = getDriver(browser);
clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();

extId = findExtension("Copy as Markdown");
Expand All @@ -63,12 +61,53 @@ public void setUp() throws IOException, InterruptedException, AWTException {
server.start();
System.out.printf("started serving on %s\n", server.getAddress());

driver.get("chrome-extension://"+e2eExtId+"/main.html?base_url=http://localhost:5566");
openE2eExtensionMainPage();
mainWindowHandle = driver.getWindowHandle();
}

private WebDriver getDriver(String browser) {
WebDriver wd;
switch (browser) {
case BROWSER_CHROME:
ChromeOptions co = new ChromeOptions();
// Fix the issue https://github.com/SeleniumHQ/selenium/issues/11750
co.addArguments("--remote-allow-origins=*");
co.addArguments("--load-extension=../chrome,./support/e2e-test-extension");
wd = new ChromeDriver(co);
break;
case BROWSER_FIREFOX:
FirefoxProfile profile = new FirefoxProfile();
profile.setPreference("intl.locale.requested","en-us");
FirefoxOptions fo = new FirefoxOptions();
fo.setProfile(profile);
FirefoxDriver fd = new FirefoxDriver(fo);
fd.installExtension(Path.of("../firefox"), true);
fd.installExtension(Path.of("./support/firefox-e2e-test-extension"), true);
wd = fd;
break;

default:
throw new IllegalArgumentException("unsupported browser: "+browser);
}

wd.manage().window().maximize();
wd.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
return wd;
}

private String findExtension(String extensionName) {
// get extension ID
String id = "";
switch (browser) {
case BROWSER_CHROME -> id = findExtensionInChrome(extensionName);
case BROWSER_FIREFOX -> id = findExtensionInFirefox(extensionName);
default -> throw new IllegalArgumentException("unsupported browser: "+browser);
}
return id;
}

private String findExtensionInChrome(String extensionName) {
assert browser.equals(BROWSER_CHROME);

driver.get("chrome://extensions/");

WebElement myExtension = null;
Expand All @@ -84,33 +123,57 @@ private String findExtension(String extensionName) {
}

if (myExtension == null) {
throw new IllegalArgumentException("extension not found");
throw new IllegalArgumentException("extension not found: "+ extensionName);
}

return myExtension.getAttribute("id");
}

@BeforeEach
private String findExtensionInFirefox(String extensionName) {
assert browser.equals(BROWSER_FIREFOX);

driver.get("about:debugging#/runtime/this-firefox");

WebElement myExtension = null;
List<WebElement> extensions = driver.findElements(By.className("debug-target-item"));

for (WebElement ext : extensions) {
String name = ext.findElement(By.className("debug-target-item__name")).getText() ;
if (Objects.equals(name, extensionName)){
myExtension = ext;
break;
}
}

if (myExtension == null) {
throw new IllegalArgumentException("extension not found: "+ extensionName);
}

WebElement manifestLink = myExtension.findElement(By.xpath(".//a[contains(@href,\"moz-extension\")]"));
if (manifestLink == null) {
throw new RuntimeException("could not find extension ID by looking for a link to manifest.json");
}

Pattern pattern = Pattern.compile("^moz-extension://([A-Za-z0-9\\-]+)/.+$");
Matcher matcher = pattern.matcher(manifestLink.getAttribute("href"));
if (!matcher.matches()) {
throw new RuntimeException("could not find extension ID by matching the link to manifest.json");
}

return matcher.toMatchResult().group(1);
}

@BeforeMethod
public void resetClipboard() {
clipboard.setContents(new StringSelection("========TEST SEPARATOR========"),null);
}

@AfterAll
@AfterClass
public void tearDown() {
driver.quit();
server.stop(0);
}

protected String findWindow(String title) {
for (Window w: windows) {
System.out.print(w);
if (Objects.equals(w.title(), title)) {
return w.handle();
}
}
return null;
}

protected void selectAll() {
Keys cmdCtrl = Platform.getCurrent().is(Platform.MAC) ? Keys.COMMAND : Keys.CONTROL;
Actions actions = new Actions(driver);
Expand All @@ -121,10 +184,33 @@ protected void selectAll() {
.perform();
}

protected void openDemoTabs() {
private void openE2eExtensionMainPage() {
driver.get(getExtensionProtocol()+"://"+e2eExtId+"/main.html?base_url=http://localhost:5566");
}

protected String getExtensionProtocol() {
return switch (browser) {
case BROWSER_CHROME -> "chrome-extension";
case BROWSER_FIREFOX -> "moz-extension";
default -> throw new IllegalStateException("Unexpected value: " + browser);
};
}

protected DemoPageData openDemoTabs(boolean groupTabs) {
driver.switchTo().window(mainWindowHandle);
openE2eExtensionMainPage();
driver.findElement(By.id("open-demo")).click();
driver.switchTo().window(mainWindowHandle);

// order matters - must group tabs first then highlight tabs,
// otherwise a new group will de-highlight tabs inside it.
if (groupTabs) {
driver.findElement(By.id("group-tabs")).click();
}
driver.findElement(By.id("highlight-tabs")).click();

String demoWindowId = driver.findElement(By.id("window-id")).getAttribute("value");
String tab0Id = driver.findElement(By.id("tab-0-id")).getAttribute("value");
return new DemoPageData(demoWindowId, tab0Id);
}
}

0 comments on commit 002f3cf

Please sign in to comment.