From 83168331840d7146d0373ddf276008e9ec750e7a Mon Sep 17 00:00:00 2001 From: Yucheng Chuang Date: Tue, 30 Apr 2024 15:26:59 +0800 Subject: [PATCH 1/4] Convert to TestNG So that we can use custom parameters --- e2e/pom.xml | 21 +++++++++---------- .../yorkxin/copyasmarkdown/e2e/BaseTest.java | 13 ++++-------- .../copyasmarkdown/e2e/ContextMenuTest.java | 9 +++----- .../e2e/KeyboardShortcutTest.java | 13 +++++------- .../copyasmarkdown/e2e/PopupPageTest.java | 10 ++++----- 5 files changed, 26 insertions(+), 40 deletions(-) diff --git a/e2e/pom.xml b/e2e/pom.xml index 865ce5a..3fd298b 100644 --- a/e2e/pom.xml +++ b/e2e/pom.xml @@ -13,7 +13,6 @@ UTF-8 21 21 - 5.10.0 1.9.19 2.24.0 @@ -27,20 +26,14 @@ io.qameta.allure - allure-junit5 + allure-testng ${allure.version} test - org.junit.jupiter - junit-jupiter-api - ${junit.version} - test - - - org.junit.jupiter - junit-jupiter-engine - ${junit.version} + org.testng + testng + 7.7.1 test @@ -61,6 +54,12 @@ 3.2.0 test + + org.testng + testng + 7.7.1 + test + diff --git a/e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/BaseTest.java b/e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/BaseTest.java index 067f0e5..68e0eaf 100644 --- a/e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/BaseTest.java +++ b/e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/BaseTest.java @@ -2,15 +2,11 @@ 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.interactions.Actions; +import org.testng.annotations.*; import java.awt.*; import java.awt.datatransfer.Clipboard; @@ -28,7 +24,6 @@ record Window(String handle, String url, String title) {} -@TestInstance(TestInstance.Lifecycle.PER_CLASS) public class BaseTest { protected WebDriver driver; private HttpServer server; @@ -39,7 +34,7 @@ public class BaseTest { protected Clipboard clipboard; List windows = new ArrayList<>(); - @BeforeAll + @BeforeClass public void setUp() throws IOException, InterruptedException, AWTException { ChromeOptions options = new ChromeOptions(); // Fix the issue https://github.com/SeleniumHQ/selenium/issues/11750 @@ -90,12 +85,12 @@ private String findExtension(String extensionName) { return myExtension.getAttribute("id"); } - @BeforeEach + @BeforeMethod public void resetClipboard() { clipboard.setContents(new StringSelection("========TEST SEPARATOR========"),null); } - @AfterAll + @AfterClass public void tearDown() { driver.quit(); server.stop(0); diff --git a/e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/ContextMenuTest.java b/e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/ContextMenuTest.java index e68fc67..035c84b 100644 --- a/e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/ContextMenuTest.java +++ b/e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/ContextMenuTest.java @@ -1,10 +1,9 @@ package org.yorkxin.copyasmarkdown.e2e; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; import org.openqa.selenium.*; import org.openqa.selenium.interactions.Actions; +import org.testng.annotations.*; +import static org.testng.Assert.*; import java.awt.*; import java.awt.datatransfer.DataFlavor; @@ -12,10 +11,8 @@ import java.awt.event.KeyEvent; import java.io.IOException; -import static org.junit.jupiter.api.Assertions.assertEquals; - public class ContextMenuTest extends BaseTest { - @BeforeEach + @BeforeMethod public void goToQaPage() { driver.get("http://localhost:5566/qa.html"); } diff --git a/e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/KeyboardShortcutTest.java b/e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/KeyboardShortcutTest.java index 0b11902..4ca4c1e 100644 --- a/e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/KeyboardShortcutTest.java +++ b/e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/KeyboardShortcutTest.java @@ -1,9 +1,10 @@ package org.yorkxin.copyasmarkdown.e2e; import io.github.sukgu.Shadow; -import org.junit.jupiter.api.*; import org.openqa.selenium.*; import org.openqa.selenium.interactions.Actions; +import org.testng.annotations.*; +import static org.testng.Assert.*; import java.awt.*; import java.awt.datatransfer.DataFlavor; @@ -13,10 +14,8 @@ import java.util.List; import java.util.Objects; -import static org.junit.jupiter.api.Assertions.assertEquals; - public class KeyboardShortcutTest extends BaseTest { - @BeforeAll + @BeforeClass public void setUp() throws IOException, InterruptedException, AWTException { super.setUp(); @@ -85,16 +84,14 @@ public void copySelectionAsMarkdown () throws AWTException, IOException, Unsuppo assertEquals(expected, clipboard.getData(DataFlavor.stringFlavor)); } - @Nested - @DisplayName("Tab Exporting") class TabExportingCases { - @BeforeEach + @BeforeMethod public void setUp() { openDemoTabs(); driver.findElement(By.id("switch-to-demo")).click(); } - @AfterEach + @AfterMethod public void teardown() { driver.switchTo().window(mainWindowHandle); driver.findElement(By.id("close-demo")).click(); diff --git a/e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/PopupPageTest.java b/e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/PopupPageTest.java index b52018f..6b29567 100644 --- a/e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/PopupPageTest.java +++ b/e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/PopupPageTest.java @@ -1,21 +1,19 @@ package org.yorkxin.copyasmarkdown.e2e; -import org.junit.jupiter.api.*; import org.openqa.selenium.By; import org.openqa.selenium.WindowType; +import org.testng.annotations.*; +import static org.testng.Assert.*; -import java.awt.*; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.UnsupportedFlavorException; import java.io.IOException; -import static org.junit.jupiter.api.Assertions.assertEquals; - public class PopupPageTest extends BaseTest { private PopupPage popupPage; private String popupHandle; - @BeforeEach + @BeforeMethod public void openPopupWindow() { openDemoTabs(); @@ -30,7 +28,7 @@ public void openPopupWindow() { popupPage = new PopupPage(driver); } - @AfterEach + @AfterMethod public void closePopupWindow() { driver.switchTo().window(popupHandle).close(); driver.switchTo().window(mainWindowHandle); From b1abc24616a250cba5f61c2704222402d1c495d2 Mon Sep 17 00:00:00 2001 From: Yucheng Chuang Date: Tue, 30 Apr 2024 21:30:06 +0800 Subject: [PATCH 2/4] Correct Firefox menu items --- firefox/create-menus.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/firefox/create-menus.js b/firefox/create-menus.js index fe1d4cb..135e809 100644 --- a/firefox/create-menus.js +++ b/firefox/create-menus.js @@ -17,21 +17,28 @@ chrome.contextMenus.create({ id: 'current-page', - title: 'Copy [Page Title](URL)', + title: 'Copy Page Link as Markdown', type: 'normal', contexts: ['page'], }); chrome.contextMenus.create({ id: 'link', - title: 'Copy [Link Content](URL)', + title: 'Copy Link as Markdown', type: 'normal', contexts: ['link'], }); chrome.contextMenus.create({ id: 'image', - title: 'Copy ![](Image URL)', // TODO: how to fetch alt text? + title: 'Copy Image as Markdown', // TODO: how to fetch alt text? type: 'normal', contexts: ['image'], }); + +chrome.contextMenus.create({ + id: 'selection-as-markdown', + title: 'Copy Selection as Markdown', + type: 'normal', + contexts: ['selection'], +}); From f24f47e5d8bc0ff6d6719256e86ba4a78e2cadce Mon Sep 17 00:00:00 2001 From: Yucheng Chuang Date: Tue, 30 Apr 2024 21:30:49 +0800 Subject: [PATCH 3/4] Improve E2E Tests 1. Test on Firefox 2. Optionally test Tab Groups feature --- .../yorkxin/copyasmarkdown/e2e/BaseTest.java | 143 +++++++-- .../copyasmarkdown/e2e/ContextMenuTest.java | 72 ++++- .../copyasmarkdown/e2e/DemoPageData.java | 3 + .../e2e/KeyboardShortcutTest.java | 280 ------------------ .../e2e/keyboardshortcut/BaseTest.java | 119 ++++++++ .../keyboardshortcut/OnPageContentsTest.java | 91 ++++++ .../keyboardshortcut/TabExportingTest.java | 171 +++++++++++ .../TabExportingWithGroupsTest.java | 184 ++++++++++++ .../copyasmarkdown/e2e/popup/BaseTest.java | 31 ++ .../e2e/{ => popup}/PopupPage.java | 2 +- .../copyasmarkdown/e2e/popup/SimpleTest.java | 34 +++ .../e2e/popup/TabExportingTest.java | 119 ++++++++ .../TabExportingWithGroupsTest.java} | 62 +--- e2e/support/e2e-test-extension/main.html | 1 + e2e/support/e2e-test-extension/main.js | 8 + .../firefox-e2e-test-extension/README.md | 5 + .../firefox-e2e-test-extension/background.js | 16 + .../firefox-e2e-test-extension/icon.png | Bin 0 -> 16613 bytes .../firefox-e2e-test-extension/main.html | 23 ++ .../firefox-e2e-test-extension/main.js | 49 +++ .../firefox-e2e-test-extension/manifest.json | 24 ++ e2e/testng-chrome.xml | 17 ++ e2e/testng-firefox.xml | 15 + e2e/testng.xml | 9 + 24 files changed, 1116 insertions(+), 362 deletions(-) create mode 100644 e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/DemoPageData.java delete mode 100644 e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/KeyboardShortcutTest.java create mode 100644 e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/keyboardshortcut/BaseTest.java create mode 100644 e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/keyboardshortcut/OnPageContentsTest.java create mode 100644 e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/keyboardshortcut/TabExportingTest.java create mode 100644 e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/keyboardshortcut/TabExportingWithGroupsTest.java create mode 100644 e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/popup/BaseTest.java rename e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/{ => popup}/PopupPage.java (96%) create mode 100644 e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/popup/SimpleTest.java create mode 100644 e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/popup/TabExportingTest.java rename e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/{PopupPageTest.java => popup/TabExportingWithGroupsTest.java} (70%) create mode 100644 e2e/support/firefox-e2e-test-extension/README.md create mode 100644 e2e/support/firefox-e2e-test-extension/background.js create mode 100644 e2e/support/firefox-e2e-test-extension/icon.png create mode 100644 e2e/support/firefox-e2e-test-extension/main.html create mode 100644 e2e/support/firefox-e2e-test-extension/main.js create mode 100644 e2e/support/firefox-e2e-test-extension/manifest.json create mode 100644 e2e/testng-chrome.xml create mode 100644 e2e/testng-firefox.xml create mode 100644 e2e/testng.xml diff --git a/e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/BaseTest.java b/e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/BaseTest.java index 68e0eaf..8ff34ec 100644 --- a/e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/BaseTest.java +++ b/e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/BaseTest.java @@ -5,6 +5,11 @@ 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.*; @@ -19,12 +24,13 @@ 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) {} - public class BaseTest { + protected String browser; protected WebDriver driver; private HttpServer server; protected String extId; @@ -32,18 +38,15 @@ public class BaseTest { protected String mainWindowHandle; protected String demoWindowHandle; protected Clipboard clipboard; - List windows = new ArrayList<>(); - @BeforeClass - 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"); @@ -58,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; @@ -79,12 +123,46 @@ 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"); } + private String findExtensionInFirefox(String extensionName) { + assert browser.equals(BROWSER_FIREFOX); + + driver.get("about:debugging#/runtime/this-firefox"); + + WebElement myExtension = null; + List 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); @@ -96,16 +174,6 @@ public void tearDown() { 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); @@ -116,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); } } diff --git a/e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/ContextMenuTest.java b/e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/ContextMenuTest.java index 035c84b..843d1dd 100644 --- a/e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/ContextMenuTest.java +++ b/e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/ContextMenuTest.java @@ -10,6 +10,7 @@ import java.awt.datatransfer.UnsupportedFlavorException; import java.awt.event.KeyEvent; import java.io.IOException; +import java.util.Objects; public class ContextMenuTest extends BaseTest { @BeforeMethod @@ -23,6 +24,8 @@ public void currentTabLink() throws AWTException, InterruptedException, IOExcept WebElement emptySpace = driver.findElement(By.id("empty-space")); actions.moveToElement(emptySpace).contextClick(emptySpace).perform(); Robot robot = new Robot(); + robot.waitForIdle(); + robot.setAutoDelay(50); // type faster to avoid selecting built-in menu item robot.keyPress(KeyEvent.VK_C); robot.keyPress(KeyEvent.VK_O); robot.keyPress(KeyEvent.VK_P); @@ -35,7 +38,7 @@ public void currentTabLink() throws AWTException, InterruptedException, IOExcept robot.keyPress(KeyEvent.VK_ENTER); Thread.sleep(1000); String expected = "[[QA] \\*\\*Hello\\*\\* \\_World\\_](http://localhost:5566/qa.html)"; - assertEquals(expected,clipboard.getData(DataFlavor.stringFlavor)); + assertEquals(clipboard.getData(DataFlavor.stringFlavor),expected); } @Test @@ -44,8 +47,17 @@ public void onPageLink() throws AWTException, InterruptedException, IOException, WebElement link = driver.findElement(By.id("link-1")); actions.moveToElement(link).contextClick(link).perform(); Robot robot = new Robot(); - enterSubMenu(robot); - robot.delay(1000); + robot.waitForIdle(); + robot.setAutoDelay(50); // type faster to avoid selecting built-in menu item + if (Objects.equals(browser, BROWSER_CHROME) && Platform.getCurrent().is(Platform.MAC)) { + // Folded on Chrome+macOS, not folded on Firefox+macOS. + // Because on macOS using the built-in layout engine, when you select a link, the text will also be + // selected, so there will be two menu items: Copy Link as Markdown, and Copy Selection as Markdown. + // On Firefox the text won't be selected, so there will be only one menu item. + // + // TODO: behavior unknown on Windows / Linux GTK / Linux KDE + enterSubMenu(robot); + } robot.keyPress(KeyEvent.VK_C); robot.keyPress(KeyEvent.VK_O); robot.keyPress(KeyEvent.VK_P); @@ -55,10 +67,22 @@ public void onPageLink() throws AWTException, InterruptedException, IOException, robot.keyPress(KeyEvent.VK_I); robot.keyPress(KeyEvent.VK_N); robot.keyPress(KeyEvent.VK_K); + robot.keyPress(KeyEvent.VK_SPACE); + robot.keyPress(KeyEvent.VK_A); + robot.keyPress(KeyEvent.VK_S); + robot.keyPress(KeyEvent.VK_SPACE); + robot.keyPress(KeyEvent.VK_M); + robot.keyPress(KeyEvent.VK_A); + robot.keyPress(KeyEvent.VK_R); + robot.keyPress(KeyEvent.VK_K); + robot.keyPress(KeyEvent.VK_D); + robot.keyPress(KeyEvent.VK_O); + robot.keyPress(KeyEvent.VK_W); + robot.keyPress(KeyEvent.VK_N); robot.keyPress(KeyEvent.VK_ENTER); Thread.sleep(1000); String expected = "[[APOLLO-13] Build A Rocket Engine](about:blank)"; - assertEquals(expected,clipboard.getData(DataFlavor.stringFlavor)); + assertEquals(clipboard.getData(DataFlavor.stringFlavor),expected); } @Test @@ -67,7 +91,8 @@ public void onPageImage() throws AWTException, InterruptedException, IOException WebElement link = driver.findElement(By.id("img-1")); actions.moveToElement(link).contextClick(link).perform(); Robot robot = new Robot(); - + robot.waitForIdle(); + robot.setAutoDelay(50); // type faster to avoid selecting built-in menu item robot.keyPress(KeyEvent.VK_C); robot.keyPress(KeyEvent.VK_O); robot.keyPress(KeyEvent.VK_P); @@ -93,7 +118,7 @@ public void onPageImage() throws AWTException, InterruptedException, IOException robot.keyPress(KeyEvent.VK_ENTER); Thread.sleep(1000); String expected = "![](http://localhost:5566/icon.png)"; - assertEquals(expected,clipboard.getData(DataFlavor.stringFlavor)); + assertEquals(clipboard.getData(DataFlavor.stringFlavor),expected); } @Test @@ -102,8 +127,9 @@ public void onPageImageInLink() throws AWTException, InterruptedException, IOExc WebElement link = driver.findElement(By.id("img-2")); actions.moveToElement(link).contextClick(link).perform(); Robot robot = new Robot(); + robot.waitForIdle(); + robot.setAutoDelay(50); // type faster to avoid selecting built-in menu item enterSubMenu(robot); - robot.delay(1000); robot.keyPress(KeyEvent.VK_C); robot.keyPress(KeyEvent.VK_O); robot.keyPress(KeyEvent.VK_P); @@ -113,10 +139,21 @@ public void onPageImageInLink() throws AWTException, InterruptedException, IOExc robot.keyPress(KeyEvent.VK_I); robot.keyPress(KeyEvent.VK_N); robot.keyPress(KeyEvent.VK_K); + robot.keyPress(KeyEvent.VK_A); + robot.keyPress(KeyEvent.VK_S); + robot.keyPress(KeyEvent.VK_SPACE); + robot.keyPress(KeyEvent.VK_M); + robot.keyPress(KeyEvent.VK_A); + robot.keyPress(KeyEvent.VK_R); + robot.keyPress(KeyEvent.VK_K); + robot.keyPress(KeyEvent.VK_D); + robot.keyPress(KeyEvent.VK_O); + robot.keyPress(KeyEvent.VK_W); + robot.keyPress(KeyEvent.VK_N); robot.keyPress(KeyEvent.VK_ENTER); Thread.sleep(1000); String expected = "[![](http://localhost:5566/icon.png)](about:blank)"; - assertEquals(expected,clipboard.getData(DataFlavor.stringFlavor)); + assertEquals(clipboard.getData(DataFlavor.stringFlavor),expected); } @Test @@ -129,6 +166,8 @@ public void onPageSelection() throws AWTException, InterruptedException, IOExcep actions.contextClick(body).perform(); Robot robot = new Robot(); + robot.waitForIdle(); + robot.setAutoDelay(50); // type faster to avoid selecting built-in menu item robot.keyPress(KeyEvent.VK_C); robot.keyPress(KeyEvent.VK_O); robot.keyPress(KeyEvent.VK_P); @@ -138,6 +177,23 @@ public void onPageSelection() throws AWTException, InterruptedException, IOExcep robot.keyPress(KeyEvent.VK_E); robot.keyPress(KeyEvent.VK_L); robot.keyPress(KeyEvent.VK_E); + robot.keyPress(KeyEvent.VK_C); + robot.keyPress(KeyEvent.VK_T); + robot.keyPress(KeyEvent.VK_I); + robot.keyPress(KeyEvent.VK_O); + robot.keyPress(KeyEvent.VK_N); + robot.keyPress(KeyEvent.VK_SPACE); + robot.keyPress(KeyEvent.VK_A); + robot.keyPress(KeyEvent.VK_S); + robot.keyPress(KeyEvent.VK_SPACE); + robot.keyPress(KeyEvent.VK_M); + robot.keyPress(KeyEvent.VK_A); + robot.keyPress(KeyEvent.VK_R); + robot.keyPress(KeyEvent.VK_K); + robot.keyPress(KeyEvent.VK_D); + robot.keyPress(KeyEvent.VK_O); + robot.keyPress(KeyEvent.VK_W); + robot.keyPress(KeyEvent.VK_N); robot.keyPress(KeyEvent.VK_ENTER); Thread.sleep(1000); String expected = """ diff --git a/e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/DemoPageData.java b/e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/DemoPageData.java new file mode 100644 index 0000000..01d2b53 --- /dev/null +++ b/e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/DemoPageData.java @@ -0,0 +1,3 @@ +package org.yorkxin.copyasmarkdown.e2e; + +public record DemoPageData(String windowId, String tab0Id) {} diff --git a/e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/KeyboardShortcutTest.java b/e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/KeyboardShortcutTest.java deleted file mode 100644 index 4ca4c1e..0000000 --- a/e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/KeyboardShortcutTest.java +++ /dev/null @@ -1,280 +0,0 @@ -package org.yorkxin.copyasmarkdown.e2e; - -import io.github.sukgu.Shadow; -import org.openqa.selenium.*; -import org.openqa.selenium.interactions.Actions; -import org.testng.annotations.*; -import static org.testng.Assert.*; - -import java.awt.*; -import java.awt.datatransfer.DataFlavor; -import java.awt.datatransfer.UnsupportedFlavorException; -import java.awt.event.KeyEvent; -import java.io.IOException; -import java.util.List; -import java.util.Objects; - -public class KeyboardShortcutTest extends BaseTest { - @BeforeClass - public void setUp() throws IOException, InterruptedException, AWTException { - super.setUp(); - - driver.switchTo().newWindow(WindowType.WINDOW).get("chrome://extensions/shortcuts"); - configureShortcutKey("current tab: [title](url)", new CharSequence[]{Keys.CONTROL, Keys.SHIFT}, "1"); - configureShortcutKey("all tabs: - [title](url)", new CharSequence[]{Keys.CONTROL, Keys.SHIFT}, "2"); - configureShortcutKey("all tabs: - [ ] [title](url)", new CharSequence[]{Keys.CONTROL, Keys.SHIFT}, "3"); - configureShortcutKey("all tabs: - title", new CharSequence[]{Keys.CONTROL, Keys.SHIFT}, "4"); - configureShortcutKey("all tabs: - url", new CharSequence[]{Keys.CONTROL, Keys.SHIFT}, "5"); - configureShortcutKey("selected tabs: - [title](url)", new CharSequence[]{Keys.CONTROL, Keys.SHIFT}, "6"); - configureShortcutKey("selected tabs: - [ ] [title](url)", new CharSequence[]{Keys.CONTROL, Keys.SHIFT}, "7"); - configureShortcutKey("selected tabs: - title", new CharSequence[]{Keys.CONTROL, Keys.SHIFT}, "8"); - configureShortcutKey("selected tabs: - url", new CharSequence[]{Keys.CONTROL, Keys.SHIFT}, "9"); - configureShortcutKey("Copy Selection as Markdown", new CharSequence[]{Keys.CONTROL, Keys.SHIFT}, "q"); - } - - @Test - public void currentTabLink() throws AWTException, IOException, UnsupportedFlavorException { - driver.get("http://localhost:5566/qa.html"); - runShortcutKeys(new int[]{KeyEvent.VK_CONTROL, KeyEvent.VK_SHIFT}, KeyEvent.VK_1); - - String expected = "[[QA] \\*\\*Hello\\*\\* \\_World\\_](http://localhost:5566/qa.html)"; - assertEquals(expected,clipboard.getData(DataFlavor.stringFlavor)); - } - - @Test - public void copySelectionAsMarkdown () throws AWTException, IOException, UnsupportedFlavorException, InterruptedException { - driver.get("http://localhost:5566/selection.html"); - selectAll(); - - runShortcutKeys(new int[]{KeyEvent.VK_CONTROL, KeyEvent.VK_SHIFT}, KeyEvent.VK_Q); - Thread.sleep(1000); - - String expected = """ - # Test: Selection - - ## Header 2 - - ### Header 3 - - #### Header 4 - - ##### Header 5 - - ###### Header 6 - - Lorem _ipsum_ **dolor sit** _amet_ **consectetur** **_adipisicing_** [elit](https://example.com/). `Corrupti fugit` officia ![ICON](http://localhost:5566/icon.png) nemo porro nam ipsam dignissimos aliquid harum officiis consectetur quasi quaerat quis repellat minus eveniet aspernatur, ratione dolorum natus. - - * * * - - - Lorem - - _ipsum_ - - **dolor sit** - - _amet_ - - xyz - 1. **consectetur** - 2. **_adipisicing_** - 3. [elit](https://example.com/) - - > Lorem _ipsum_ **dolor sit** _amet_ **consectetur** **_adipisicing_** [elit](https://example.com/). `Corrupti fugit` officia nemo porro nam ipsam dignissimos aliquid harum officiis consectetur quasi quaerat quis repellat minus eveniet aspernatur, ratione dolorum natus. - - \s - Lorem ipsum dolor sit, amet consectetur adipisicing elit.\s - Ratione nobis aperiam unde magni libero minima eaque at placeat\s - molestiae odio! Ducimus ullam, nisi nostrum qui libero quidem culpa a ab."""; - assertEquals(expected, clipboard.getData(DataFlavor.stringFlavor)); - } - - class TabExportingCases { - @BeforeMethod - public void setUp() { - openDemoTabs(); - driver.findElement(By.id("switch-to-demo")).click(); - } - - @AfterMethod - public void teardown() { - driver.switchTo().window(mainWindowHandle); - driver.findElement(By.id("close-demo")).click(); - } - - @Test - public void allTabsLink() throws AWTException, IOException, UnsupportedFlavorException, InterruptedException { - runShortcutKeys(new int[]{KeyEvent.VK_CONTROL, KeyEvent.VK_SHIFT}, KeyEvent.VK_2); - - String expected = """ - - [Page 0 - Copy as Markdown](http://localhost:5566/0.html) - - Group 1 - - [Page 1 - Copy as Markdown](http://localhost:5566/1.html) - - [Page 2 - Copy as Markdown](http://localhost:5566/2.html) - - [Page 3 - Copy as Markdown](http://localhost:5566/3.html) - - Untitled green group - - [Page 4 - Copy as Markdown](http://localhost:5566/4.html) - - [Page 5 - Copy as Markdown](http://localhost:5566/5.html)"""; - - assertEquals(expected, clipboard.getData(DataFlavor.stringFlavor)); - } - - @Test - public void allTabsTaskList() throws AWTException, IOException, UnsupportedFlavorException { - runShortcutKeys(new int[]{KeyEvent.VK_CONTROL, KeyEvent.VK_SHIFT}, KeyEvent.VK_3); - - String expected = """ - - [ ] [Page 0 - Copy as Markdown](http://localhost:5566/0.html) - - [ ] Group 1 - - [ ] [Page 1 - Copy as Markdown](http://localhost:5566/1.html) - - [ ] [Page 2 - Copy as Markdown](http://localhost:5566/2.html) - - [ ] [Page 3 - Copy as Markdown](http://localhost:5566/3.html) - - [ ] Untitled green group - - [ ] [Page 4 - Copy as Markdown](http://localhost:5566/4.html) - - [ ] [Page 5 - Copy as Markdown](http://localhost:5566/5.html)"""; - - assertEquals(expected, clipboard.getData(DataFlavor.stringFlavor)); - } - - @Test - public void allTabsTitle() throws AWTException, IOException, UnsupportedFlavorException { - runShortcutKeys(new int[]{KeyEvent.VK_CONTROL, KeyEvent.VK_SHIFT}, KeyEvent.VK_4); - - String expected = """ - - Page 0 - Copy as Markdown - - Group 1 - - Page 1 - Copy as Markdown - - Page 2 - Copy as Markdown - - Page 3 - Copy as Markdown - - Untitled green group - - Page 4 - Copy as Markdown - - Page 5 - Copy as Markdown"""; - - assertEquals(expected, clipboard.getData(DataFlavor.stringFlavor)); - } - - @Test - public void allTabsUrl() throws AWTException, IOException, UnsupportedFlavorException { - runShortcutKeys(new int[]{KeyEvent.VK_CONTROL, KeyEvent.VK_SHIFT}, KeyEvent.VK_5); - - String expected = """ - - http://localhost:5566/0.html - - Group 1 - - http://localhost:5566/1.html - - http://localhost:5566/2.html - - http://localhost:5566/3.html - - Untitled green group - - http://localhost:5566/4.html - - http://localhost:5566/5.html"""; - - assertEquals(expected, clipboard.getData(DataFlavor.stringFlavor)); - } - - @Test - public void highlightedTabsLink() throws AWTException, IOException, UnsupportedFlavorException, InterruptedException { - runShortcutKeys(new int[]{KeyEvent.VK_CONTROL, KeyEvent.VK_SHIFT}, KeyEvent.VK_6); - - String expected = """ - - [Page 0 - Copy as Markdown](http://localhost:5566/0.html) - - Group 1 - - [Page 2 - Copy as Markdown](http://localhost:5566/2.html) - - Untitled green group - - [Page 4 - Copy as Markdown](http://localhost:5566/4.html)"""; - - assertEquals(expected, clipboard.getData(DataFlavor.stringFlavor)); - } - - @Test - public void highlightedTabsTaskList() throws AWTException, IOException, UnsupportedFlavorException { - runShortcutKeys(new int[]{KeyEvent.VK_CONTROL, KeyEvent.VK_SHIFT}, KeyEvent.VK_7); - - String expected = """ - - [ ] [Page 0 - Copy as Markdown](http://localhost:5566/0.html) - - [ ] Group 1 - - [ ] [Page 2 - Copy as Markdown](http://localhost:5566/2.html) - - [ ] Untitled green group - - [ ] [Page 4 - Copy as Markdown](http://localhost:5566/4.html)"""; - - assertEquals(expected, clipboard.getData(DataFlavor.stringFlavor)); - } - - @Test - public void highlightedTabsTitle() throws AWTException, IOException, UnsupportedFlavorException { - runShortcutKeys(new int[]{KeyEvent.VK_CONTROL, KeyEvent.VK_SHIFT}, KeyEvent.VK_8); - - String expected = """ - - Page 0 - Copy as Markdown - - Group 1 - - Page 2 - Copy as Markdown - - Untitled green group - - Page 4 - Copy as Markdown"""; - - assertEquals(expected, clipboard.getData(DataFlavor.stringFlavor)); - } - - @Test - public void highlightedTabsUrl() throws AWTException, IOException, UnsupportedFlavorException { - runShortcutKeys(new int[]{KeyEvent.VK_CONTROL, KeyEvent.VK_SHIFT}, KeyEvent.VK_9); - - String expected = """ - - http://localhost:5566/0.html - - Group 1 - - http://localhost:5566/2.html - - Untitled green group - - http://localhost:5566/4.html"""; - - assertEquals(expected, clipboard.getData(DataFlavor.stringFlavor)); - } - } - - private static void runShortcutKeys(int[] modifiers, int key) throws AWTException { - Robot robot = new Robot(); - for (int modifier : modifiers) { - robot.keyPress(modifier); - robot.delay(200); - } - robot.keyPress(key); - for (int modifier : modifiers) { - robot.keyRelease(modifier); - } - robot.delay(1000); - } - - private void configureShortcutKey(String commandName, CharSequence[] modifiers, CharSequence key) throws AWTException, InterruptedException { - // Max 4 shortcut keys can be specified in the manifest.json file, - // so we have to navigate to chrome://extension/shortcuts and configure them in runtime. - - if (!Objects.equals(driver.getCurrentUrl(), "chrome://extensions/shortcuts")) { - throw new InvalidArgumentException("this function only works in chrome://extensions/shortcuts page"); - } - Shadow shadow = new Shadow(driver); - List commandEntries = shadow.findElements("div.command-entry"); - - WebElement cmdEntry = null; - for (WebElement entry : commandEntries) { - String name = entry.findElement(By.className("command-name")).getText() ; - if (Objects.equals(name, commandName)){ - cmdEntry = entry; - break; - } - } - - if (cmdEntry == null) { - throw new IllegalArgumentException("no such command: "+commandName); - } - - WebElement editButton = shadow.findElement(cmdEntry, "#edit"); - WebElement inputBox = shadow.findElement(cmdEntry, "#input"); - - // NOTE: for some reason, Robot does not work here, so using Actions to trigger DOM key events. - - Actions actions = new Actions(driver); - actions.scrollToElement(editButton) - .click(editButton) - .click(inputBox); - - for (CharSequence modifier : modifiers) { - actions = actions.keyDown(modifier); - } - actions.keyDown(key); - for (CharSequence modifier : modifiers) { - actions = actions.keyUp(modifier); - } - actions.perform(); - } -} diff --git a/e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/keyboardshortcut/BaseTest.java b/e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/keyboardshortcut/BaseTest.java new file mode 100644 index 0000000..6389800 --- /dev/null +++ b/e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/keyboardshortcut/BaseTest.java @@ -0,0 +1,119 @@ +package org.yorkxin.copyasmarkdown.e2e.keyboardshortcut; + +import io.github.sukgu.Shadow; +import org.openqa.selenium.*; +import org.openqa.selenium.interactions.Actions; +import org.testng.annotations.*; + +import static org.testng.Assert.*; + +import java.awt.*; +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.awt.event.KeyEvent; +import java.io.IOException; +import java.util.List; +import java.util.Objects; + +public class BaseTest extends org.yorkxin.copyasmarkdown.e2e.BaseTest { + void openChromeKeyboardShortcutsPage() { + driver.switchTo().newWindow(WindowType.WINDOW).get("chrome://extensions/shortcuts"); + } + + void openFirefoxKeyboardShortcutsPage() { + driver.switchTo().newWindow(WindowType.WINDOW).get("about:addons"); + driver.findElement(By.cssSelector("button[action='page-options']")).click(); + + // Choose the last item on the menu which opens the keyboard shortcuts options. + + Actions actions = new Actions(driver); + actions.keyDown(Keys.DOWN) + .keyDown(Keys.DOWN) + .keyDown(Keys.DOWN) + .keyDown(Keys.DOWN) + .keyDown(Keys.DOWN) + .keyDown(Keys.DOWN) + .keyDown(Keys.DOWN) + .keyDown(Keys.ENTER) + .perform(); + + driver.findElements(By.cssSelector("button[data-l10n-id=\"shortcuts-card-expand-button\"]")).forEach(e -> e.click()); + } + + static void runShortcutKeys(int[] modifiers, int key) throws AWTException { + Robot robot = new Robot(); + for (int modifier : modifiers) { + robot.keyPress(modifier); + robot.delay(200); + } + robot.keyPress(key); + for (int modifier : modifiers) { + robot.keyRelease(modifier); + } + robot.delay(1000); + } + + void setShortcutKeyInChrome(String commandName, CharSequence[] modifiers, CharSequence key) throws AWTException, InterruptedException { + // Max 4 shortcut keys can be specified in the manifest.json file, + // so we have to navigate to chrome://extension/shortcuts and configure them in runtime. + + if (!Objects.equals(driver.getCurrentUrl(), "chrome://extensions/shortcuts")) { + throw new InvalidArgumentException("this function only works in chrome://extensions/shortcuts page"); + } + Shadow shadow = new Shadow(driver); + List commandEntries = shadow.findElements("div.command-entry"); + + WebElement cmdEntry = null; + for (WebElement entry : commandEntries) { + String name = entry.findElement(By.className("command-name")).getText() ; + if (Objects.equals(name, commandName)){ + cmdEntry = entry; + break; + } + } + + if (cmdEntry == null) { + throw new IllegalArgumentException("no such command: "+commandName); + } + + WebElement editButton = shadow.findElement(cmdEntry, "#edit"); + WebElement inputBox = shadow.findElement(cmdEntry, "#input"); + + // NOTE: for some reason, Robot does not work here, so using Actions to trigger DOM key events. + + Actions actions = new Actions(driver); + actions.scrollToElement(editButton) + .click(editButton) + .click(inputBox); + + for (CharSequence modifier : modifiers) { + actions = actions.keyDown(modifier); + } + actions.keyDown(key); + for (CharSequence modifier : modifiers) { + actions = actions.keyUp(modifier); + } + actions.perform(); + } + + void setShortcutKeyInFirefox(String commandId, CharSequence[] modifiers, CharSequence key) { + WebElement cmdEntry = driver.findElement(By.xpath("//input[@name=\""+commandId+"\"]")); + + if (cmdEntry == null) { + throw new IllegalArgumentException("no such command: "+commandId); + } + + Actions actions = new Actions(driver); + actions.scrollToElement(cmdEntry) + .click(cmdEntry); + + for (CharSequence modifier : modifiers) { + actions = actions.keyDown(modifier); + } + actions.keyDown(key); + for (CharSequence modifier : modifiers) { + actions = actions.keyUp(modifier); + } + actions.perform(); + } +} diff --git a/e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/keyboardshortcut/OnPageContentsTest.java b/e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/keyboardshortcut/OnPageContentsTest.java new file mode 100644 index 0000000..b4b6578 --- /dev/null +++ b/e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/keyboardshortcut/OnPageContentsTest.java @@ -0,0 +1,91 @@ +package org.yorkxin.copyasmarkdown.e2e.keyboardshortcut; + +import org.openqa.selenium.By; +import org.openqa.selenium.Keys; +import org.openqa.selenium.WindowType; +import org.openqa.selenium.interactions.Actions; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import java.awt.*; +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.awt.event.KeyEvent; +import java.io.IOException; + +import static org.testng.Assert.assertEquals; + +public class OnPageContentsTest extends BaseTest{ + @BeforeClass + public void configureKeyboardShortcuts() throws InterruptedException, AWTException { + switch (browser) { + case BROWSER_CHROME -> configureKeyboardShortcutsInChrome(); + case BROWSER_FIREFOX -> configureKeyboardShortcutsInFirefox(); + default -> throw new IllegalStateException("Unexpected browser: " + browser); + } + } + + public void configureKeyboardShortcutsInChrome() throws InterruptedException, AWTException { + openChromeKeyboardShortcutsPage(); + setShortcutKeyInChrome("current tab: [title](url)", new CharSequence[]{Keys.CONTROL, Keys.SHIFT}, "q"); + setShortcutKeyInChrome("Copy Selection as Markdown", new CharSequence[]{Keys.CONTROL, Keys.SHIFT}, "p"); + } + + public void configureKeyboardShortcutsInFirefox() throws InterruptedException, AWTException { + openFirefoxKeyboardShortcutsPage(); + setShortcutKeyInFirefox("current-tab-link", new CharSequence[]{Keys.CONTROL, Keys.SHIFT}, "q"); + setShortcutKeyInFirefox("selection-as-markdown", new CharSequence[]{Keys.CONTROL, Keys.SHIFT}, "p"); + } + + @Test + public void currentTabLink() throws AWTException, IOException, UnsupportedFlavorException { + driver.get("http://localhost:5566/qa.html"); + runShortcutKeys(new int[]{KeyEvent.VK_CONTROL, KeyEvent.VK_SHIFT}, KeyEvent.VK_Q); + + String expected = "[[QA] \\*\\*Hello\\*\\* \\_World\\_](http://localhost:5566/qa.html)"; + assertEquals(clipboard.getData(DataFlavor.stringFlavor),expected); + } + + @Test + public void copySelectionAsMarkdown () throws AWTException, IOException, UnsupportedFlavorException, InterruptedException { + driver.get("http://localhost:5566/selection.html"); + selectAll(); + + runShortcutKeys(new int[]{KeyEvent.VK_CONTROL, KeyEvent.VK_SHIFT}, KeyEvent.VK_P); + Thread.sleep(1000); + + String expected = """ + # Test: Selection + + ## Header 2 + + ### Header 3 + + #### Header 4 + + ##### Header 5 + + ###### Header 6 + + Lorem _ipsum_ **dolor sit** _amet_ **consectetur** **_adipisicing_** [elit](https://example.com/). `Corrupti fugit` officia ![ICON](http://localhost:5566/icon.png) nemo porro nam ipsam dignissimos aliquid harum officiis consectetur quasi quaerat quis repellat minus eveniet aspernatur, ratione dolorum natus. + + * * * + + - Lorem + - _ipsum_ + - **dolor sit** + - _amet_ + - xyz + 1. **consectetur** + 2. **_adipisicing_** + 3. [elit](https://example.com/) + + > Lorem _ipsum_ **dolor sit** _amet_ **consectetur** **_adipisicing_** [elit](https://example.com/). `Corrupti fugit` officia nemo porro nam ipsam dignissimos aliquid harum officiis consectetur quasi quaerat quis repellat minus eveniet aspernatur, ratione dolorum natus. + + \s + Lorem ipsum dolor sit, amet consectetur adipisicing elit.\s + Ratione nobis aperiam unde magni libero minima eaque at placeat\s + molestiae odio! Ducimus ullam, nisi nostrum qui libero quidem culpa a ab."""; + assertEquals(expected, clipboard.getData(DataFlavor.stringFlavor)); + } +} diff --git a/e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/keyboardshortcut/TabExportingTest.java b/e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/keyboardshortcut/TabExportingTest.java new file mode 100644 index 0000000..1373326 --- /dev/null +++ b/e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/keyboardshortcut/TabExportingTest.java @@ -0,0 +1,171 @@ +package org.yorkxin.copyasmarkdown.e2e.keyboardshortcut; + +import org.openqa.selenium.By; +import org.openqa.selenium.Keys; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import java.awt.*; +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.awt.event.KeyEvent; +import java.io.IOException; + +import static org.testng.Assert.assertEquals; + +public class TabExportingTest extends BaseTest { + @BeforeClass + public void configureKeyboardShortcuts() throws InterruptedException, AWTException { + switch (browser) { + case BROWSER_CHROME -> configureKeyboardShortcutsInChrome(); + case BROWSER_FIREFOX -> configureKeyboardShortcutsInFirefox(); + default -> throw new IllegalStateException("Unexpected browser: " + browser); + } + } + + public void configureKeyboardShortcutsInChrome() throws InterruptedException, AWTException { + openChromeKeyboardShortcutsPage(); + setShortcutKeyInChrome("all tabs: - [title](url)", new CharSequence[]{Keys.CONTROL, Keys.SHIFT}, "w"); + setShortcutKeyInChrome("all tabs: - [ ] [title](url)", new CharSequence[]{Keys.CONTROL, Keys.SHIFT}, "e"); + setShortcutKeyInChrome("all tabs: - title", new CharSequence[]{Keys.CONTROL, Keys.SHIFT}, "r"); + setShortcutKeyInChrome("all tabs: - url", new CharSequence[]{Keys.CONTROL, Keys.SHIFT}, "t"); + setShortcutKeyInChrome("selected tabs: - [title](url)", new CharSequence[]{Keys.CONTROL, Keys.SHIFT}, "y"); + setShortcutKeyInChrome("selected tabs: - [ ] [title](url)", new CharSequence[]{Keys.CONTROL, Keys.SHIFT}, "u"); + setShortcutKeyInChrome("selected tabs: - title", new CharSequence[]{Keys.CONTROL, Keys.SHIFT}, "i"); + setShortcutKeyInChrome("selected tabs: - url", new CharSequence[]{Keys.CONTROL, Keys.SHIFT}, "o"); + } + + public void configureKeyboardShortcutsInFirefox() throws InterruptedException, AWTException { + openFirefoxKeyboardShortcutsPage(); + setShortcutKeyInFirefox("all-tabs-link-as-list", new CharSequence[]{Keys.CONTROL, Keys.SHIFT}, "w"); + setShortcutKeyInFirefox("all-tabs-link-as-task-list", new CharSequence[]{Keys.CONTROL, Keys.SHIFT}, "e"); + setShortcutKeyInFirefox("all-tabs-title-as-list", new CharSequence[]{Keys.CONTROL, Keys.SHIFT}, "r"); + setShortcutKeyInFirefox("all-tabs-url-as-list", new CharSequence[]{Keys.CONTROL, Keys.SHIFT}, "t"); + setShortcutKeyInFirefox("highlighted-tabs-link-as-list", new CharSequence[]{Keys.CONTROL, Keys.SHIFT}, "y"); + setShortcutKeyInFirefox("highlighted-tabs-link-as-task-list", new CharSequence[]{Keys.CONTROL, Keys.SHIFT}, "u"); + setShortcutKeyInFirefox("highlighted-tabs-title-as-list", new CharSequence[]{Keys.CONTROL, Keys.SHIFT}, "i"); + setShortcutKeyInFirefox("highlighted-tabs-url-as-list", new CharSequence[]{Keys.CONTROL, Keys.SHIFT}, "o"); + } + + @BeforeMethod + public void setUp() { + openDemoTabs(false); + driver.findElement(By.id("switch-to-demo")).click(); + } + + @AfterMethod + public void teardown() { + driver.switchTo().window(mainWindowHandle); + driver.findElement(By.id("close-demo")).click(); + } + + @Test + public void allTabsLink() throws AWTException, IOException, UnsupportedFlavorException, InterruptedException { + runShortcutKeys(new int[]{KeyEvent.VK_CONTROL, KeyEvent.VK_SHIFT}, KeyEvent.VK_W); + + String expected = """ + - [Page 0 - Copy as Markdown](http://localhost:5566/0.html) + - [Page 1 - Copy as Markdown](http://localhost:5566/1.html) + - [Page 2 - Copy as Markdown](http://localhost:5566/2.html) + - [Page 3 - Copy as Markdown](http://localhost:5566/3.html) + - [Page 4 - Copy as Markdown](http://localhost:5566/4.html) + - [Page 5 - Copy as Markdown](http://localhost:5566/5.html)"""; + + assertEquals(expected, clipboard.getData(DataFlavor.stringFlavor)); + } + + @Test + public void allTabsTaskList() throws AWTException, IOException, UnsupportedFlavorException { + runShortcutKeys(new int[]{KeyEvent.VK_CONTROL, KeyEvent.VK_SHIFT}, KeyEvent.VK_E); + + String expected = """ + - [ ] [Page 0 - Copy as Markdown](http://localhost:5566/0.html) + - [ ] [Page 1 - Copy as Markdown](http://localhost:5566/1.html) + - [ ] [Page 2 - Copy as Markdown](http://localhost:5566/2.html) + - [ ] [Page 3 - Copy as Markdown](http://localhost:5566/3.html) + - [ ] [Page 4 - Copy as Markdown](http://localhost:5566/4.html) + - [ ] [Page 5 - Copy as Markdown](http://localhost:5566/5.html)"""; + + assertEquals(expected, clipboard.getData(DataFlavor.stringFlavor)); + } + + @Test + public void allTabsTitle() throws AWTException, IOException, UnsupportedFlavorException { + runShortcutKeys(new int[]{KeyEvent.VK_CONTROL, KeyEvent.VK_SHIFT}, KeyEvent.VK_R); + + String expected = """ + - Page 0 - Copy as Markdown + - Page 1 - Copy as Markdown + - Page 2 - Copy as Markdown + - Page 3 - Copy as Markdown + - Page 4 - Copy as Markdown + - Page 5 - Copy as Markdown"""; + + assertEquals(expected, clipboard.getData(DataFlavor.stringFlavor)); + } + + @Test + public void allTabsUrl() throws AWTException, IOException, UnsupportedFlavorException { + runShortcutKeys(new int[]{KeyEvent.VK_CONTROL, KeyEvent.VK_SHIFT}, KeyEvent.VK_T); + + String expected = """ + - http://localhost:5566/0.html + - http://localhost:5566/1.html + - http://localhost:5566/2.html + - http://localhost:5566/3.html + - http://localhost:5566/4.html + - http://localhost:5566/5.html"""; + + assertEquals(expected, clipboard.getData(DataFlavor.stringFlavor)); + } + + @Test + public void highlightedTabsLink() throws AWTException, IOException, UnsupportedFlavorException, InterruptedException { + runShortcutKeys(new int[]{KeyEvent.VK_CONTROL, KeyEvent.VK_SHIFT}, KeyEvent.VK_Y); + + String expected = """ + - [Page 0 - Copy as Markdown](http://localhost:5566/0.html) + - [Page 2 - Copy as Markdown](http://localhost:5566/2.html) + - [Page 4 - Copy as Markdown](http://localhost:5566/4.html)"""; + + assertEquals(expected, clipboard.getData(DataFlavor.stringFlavor)); + } + + @Test + public void highlightedTabsTaskList() throws AWTException, IOException, UnsupportedFlavorException { + runShortcutKeys(new int[]{KeyEvent.VK_CONTROL, KeyEvent.VK_SHIFT}, KeyEvent.VK_U); + + String expected = """ + - [ ] [Page 0 - Copy as Markdown](http://localhost:5566/0.html) + - [ ] [Page 2 - Copy as Markdown](http://localhost:5566/2.html) + - [ ] [Page 4 - Copy as Markdown](http://localhost:5566/4.html)"""; + + assertEquals(expected, clipboard.getData(DataFlavor.stringFlavor)); + } + + @Test + public void highlightedTabsTitle() throws AWTException, IOException, UnsupportedFlavorException { + runShortcutKeys(new int[]{KeyEvent.VK_CONTROL, KeyEvent.VK_SHIFT}, KeyEvent.VK_I); + + String expected = """ + - Page 0 - Copy as Markdown + - Page 2 - Copy as Markdown + - Page 4 - Copy as Markdown"""; + + assertEquals(expected, clipboard.getData(DataFlavor.stringFlavor)); + } + + @Test + public void highlightedTabsUrl() throws AWTException, IOException, UnsupportedFlavorException { + runShortcutKeys(new int[]{KeyEvent.VK_CONTROL, KeyEvent.VK_SHIFT}, KeyEvent.VK_O); + + String expected = """ + - http://localhost:5566/0.html + - http://localhost:5566/2.html + - http://localhost:5566/4.html"""; + + assertEquals(expected, clipboard.getData(DataFlavor.stringFlavor)); + } +} diff --git a/e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/keyboardshortcut/TabExportingWithGroupsTest.java b/e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/keyboardshortcut/TabExportingWithGroupsTest.java new file mode 100644 index 0000000..4bb43ca --- /dev/null +++ b/e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/keyboardshortcut/TabExportingWithGroupsTest.java @@ -0,0 +1,184 @@ +package org.yorkxin.copyasmarkdown.e2e.keyboardshortcut; + +import org.openqa.selenium.By; +import org.openqa.selenium.Keys; +import org.testng.annotations.*; + +import java.awt.*; +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.awt.event.KeyEvent; +import java.io.IOException; + +import static org.testng.Assert.assertEquals; + +public class TabExportingWithGroupsTest extends BaseTest { + @BeforeClass + public void configureKeyboardShortcuts() throws InterruptedException, AWTException { + switch (browser) { + case BROWSER_CHROME -> configureKeyboardShortcutsInChrome(); + case BROWSER_FIREFOX -> configureKeyboardShortcutsInFirefox(); + default -> throw new IllegalStateException("Unexpected browser: " + browser); + } + } + + public void configureKeyboardShortcutsInChrome() throws InterruptedException, AWTException { + openChromeKeyboardShortcutsPage(); + setShortcutKeyInChrome("all tabs: - [title](url)", new CharSequence[]{Keys.CONTROL, Keys.SHIFT}, "w"); + setShortcutKeyInChrome("all tabs: - [ ] [title](url)", new CharSequence[]{Keys.CONTROL, Keys.SHIFT}, "e"); + setShortcutKeyInChrome("all tabs: - title", new CharSequence[]{Keys.CONTROL, Keys.SHIFT}, "r"); + setShortcutKeyInChrome("all tabs: - url", new CharSequence[]{Keys.CONTROL, Keys.SHIFT}, "t"); + setShortcutKeyInChrome("selected tabs: - [title](url)", new CharSequence[]{Keys.CONTROL, Keys.SHIFT}, "y"); + setShortcutKeyInChrome("selected tabs: - [ ] [title](url)", new CharSequence[]{Keys.CONTROL, Keys.SHIFT}, "u"); + setShortcutKeyInChrome("selected tabs: - title", new CharSequence[]{Keys.CONTROL, Keys.SHIFT}, "i"); + setShortcutKeyInChrome("selected tabs: - url", new CharSequence[]{Keys.CONTROL, Keys.SHIFT}, "o"); + } + + public void configureKeyboardShortcutsInFirefox() throws InterruptedException, AWTException { + openFirefoxKeyboardShortcutsPage(); + setShortcutKeyInFirefox("all-tabs-link-as-list", new CharSequence[]{Keys.CONTROL, Keys.SHIFT}, "w"); + setShortcutKeyInFirefox("all-tabs-link-as-task-list", new CharSequence[]{Keys.CONTROL, Keys.SHIFT}, "e"); + setShortcutKeyInFirefox("all-tabs-title-as-list", new CharSequence[]{Keys.CONTROL, Keys.SHIFT}, "r"); + setShortcutKeyInFirefox("all-tabs-url-as-list", new CharSequence[]{Keys.CONTROL, Keys.SHIFT}, "t"); + setShortcutKeyInFirefox("highlighted-tabs-link-as-list", new CharSequence[]{Keys.CONTROL, Keys.SHIFT}, "y"); + setShortcutKeyInFirefox("highlighted-tabs-link-as-task-list", new CharSequence[]{Keys.CONTROL, Keys.SHIFT}, "u"); + setShortcutKeyInFirefox("highlighted-tabs-title-as-list", new CharSequence[]{Keys.CONTROL, Keys.SHIFT}, "i"); + setShortcutKeyInFirefox("highlighted-tabs-url-as-list", new CharSequence[]{Keys.CONTROL, Keys.SHIFT}, "o"); + } + + @BeforeMethod + public void setUp() { + openDemoTabs(true); + driver.findElement(By.id("switch-to-demo")).click(); + } + + @AfterMethod + public void teardown() { + driver.switchTo().window(mainWindowHandle); + driver.findElement(By.id("close-demo")).click(); + } + + @Test + public void allTabsLink() throws AWTException, IOException, UnsupportedFlavorException, InterruptedException { + runShortcutKeys(new int[]{KeyEvent.VK_CONTROL, KeyEvent.VK_SHIFT}, KeyEvent.VK_W); + + String expected = """ + - [Page 0 - Copy as Markdown](http://localhost:5566/0.html) + - Group 1 + - [Page 1 - Copy as Markdown](http://localhost:5566/1.html) + - [Page 2 - Copy as Markdown](http://localhost:5566/2.html) + - [Page 3 - Copy as Markdown](http://localhost:5566/3.html) + - Untitled green group + - [Page 4 - Copy as Markdown](http://localhost:5566/4.html) + - [Page 5 - Copy as Markdown](http://localhost:5566/5.html)"""; + + assertEquals(expected, clipboard.getData(DataFlavor.stringFlavor)); + } + + @Test + public void allTabsTaskList() throws AWTException, IOException, UnsupportedFlavorException { + runShortcutKeys(new int[]{KeyEvent.VK_CONTROL, KeyEvent.VK_SHIFT}, KeyEvent.VK_E); + + String expected = """ + - [ ] [Page 0 - Copy as Markdown](http://localhost:5566/0.html) + - [ ] Group 1 + - [ ] [Page 1 - Copy as Markdown](http://localhost:5566/1.html) + - [ ] [Page 2 - Copy as Markdown](http://localhost:5566/2.html) + - [ ] [Page 3 - Copy as Markdown](http://localhost:5566/3.html) + - [ ] Untitled green group + - [ ] [Page 4 - Copy as Markdown](http://localhost:5566/4.html) + - [ ] [Page 5 - Copy as Markdown](http://localhost:5566/5.html)"""; + + assertEquals(expected, clipboard.getData(DataFlavor.stringFlavor)); + } + + @Test + public void allTabsTitle() throws AWTException, IOException, UnsupportedFlavorException { + runShortcutKeys(new int[]{KeyEvent.VK_CONTROL, KeyEvent.VK_SHIFT}, KeyEvent.VK_R); + + String expected = """ + - Page 0 - Copy as Markdown + - Group 1 + - Page 1 - Copy as Markdown + - Page 2 - Copy as Markdown + - Page 3 - Copy as Markdown + - Untitled green group + - Page 4 - Copy as Markdown + - Page 5 - Copy as Markdown"""; + + assertEquals(expected, clipboard.getData(DataFlavor.stringFlavor)); + } + + @Test + public void allTabsUrl() throws AWTException, IOException, UnsupportedFlavorException { + runShortcutKeys(new int[]{KeyEvent.VK_CONTROL, KeyEvent.VK_SHIFT}, KeyEvent.VK_T); + + String expected = """ + - http://localhost:5566/0.html + - Group 1 + - http://localhost:5566/1.html + - http://localhost:5566/2.html + - http://localhost:5566/3.html + - Untitled green group + - http://localhost:5566/4.html + - http://localhost:5566/5.html"""; + + assertEquals(expected, clipboard.getData(DataFlavor.stringFlavor)); + } + + @Test + public void highlightedTabsLink() throws AWTException, IOException, UnsupportedFlavorException, InterruptedException { + runShortcutKeys(new int[]{KeyEvent.VK_CONTROL, KeyEvent.VK_SHIFT}, KeyEvent.VK_Y); + + String expected = """ + - [Page 0 - Copy as Markdown](http://localhost:5566/0.html) + - Group 1 + - [Page 2 - Copy as Markdown](http://localhost:5566/2.html) + - Untitled green group + - [Page 4 - Copy as Markdown](http://localhost:5566/4.html)"""; + + assertEquals(expected, clipboard.getData(DataFlavor.stringFlavor)); + } + + @Test + public void highlightedTabsTaskList() throws AWTException, IOException, UnsupportedFlavorException { + runShortcutKeys(new int[]{KeyEvent.VK_CONTROL, KeyEvent.VK_SHIFT}, KeyEvent.VK_U); + + String expected = """ + - [ ] [Page 0 - Copy as Markdown](http://localhost:5566/0.html) + - [ ] Group 1 + - [ ] [Page 2 - Copy as Markdown](http://localhost:5566/2.html) + - [ ] Untitled green group + - [ ] [Page 4 - Copy as Markdown](http://localhost:5566/4.html)"""; + + assertEquals(expected, clipboard.getData(DataFlavor.stringFlavor)); + } + + @Test + public void highlightedTabsTitle() throws AWTException, IOException, UnsupportedFlavorException { + runShortcutKeys(new int[]{KeyEvent.VK_CONTROL, KeyEvent.VK_SHIFT}, KeyEvent.VK_I); + + String expected = """ + - Page 0 - Copy as Markdown + - Group 1 + - Page 2 - Copy as Markdown + - Untitled green group + - Page 4 - Copy as Markdown"""; + + assertEquals(expected, clipboard.getData(DataFlavor.stringFlavor)); + } + + @Test + public void highlightedTabsUrl() throws AWTException, IOException, UnsupportedFlavorException { + runShortcutKeys(new int[]{KeyEvent.VK_CONTROL, KeyEvent.VK_SHIFT}, KeyEvent.VK_O); + + String expected = """ + - http://localhost:5566/0.html + - Group 1 + - http://localhost:5566/2.html + - Untitled green group + - http://localhost:5566/4.html"""; + + assertEquals(expected, clipboard.getData(DataFlavor.stringFlavor)); + } +} diff --git a/e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/popup/BaseTest.java b/e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/popup/BaseTest.java new file mode 100644 index 0000000..5f11fd9 --- /dev/null +++ b/e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/popup/BaseTest.java @@ -0,0 +1,31 @@ +package org.yorkxin.copyasmarkdown.e2e.popup; + +import org.openqa.selenium.By; +import org.openqa.selenium.WindowType; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.yorkxin.copyasmarkdown.e2e.DemoPageData; + +import static org.testng.Assert.assertEquals; + +public class BaseTest extends org.yorkxin.copyasmarkdown.e2e.BaseTest { + protected PopupPage popupPage; + protected String popupHandle; + +// @BeforeMethod + public void openPopupWindow(DemoPageData dpd) { + // Open popup + driver.switchTo().newWindow(WindowType.WINDOW) + .get(getExtensionProtocol() + "://" + extId + "/dist/ui/popup.html?window=" + dpd.windowId() + "&tab=" + dpd.tab0Id() + "&keep_open=1"); + + popupHandle = driver.getWindowHandle(); + popupPage = new PopupPage(driver); + } + + @AfterMethod + public void closePopupWindow() { + driver.switchTo().window(popupHandle).close(); + driver.switchTo().window(mainWindowHandle); + driver.findElement(By.id("close-demo")).click(); + } +} diff --git a/e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/PopupPage.java b/e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/popup/PopupPage.java similarity index 96% rename from e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/PopupPage.java rename to e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/popup/PopupPage.java index afb3c97..8d53a46 100644 --- a/e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/PopupPage.java +++ b/e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/popup/PopupPage.java @@ -1,4 +1,4 @@ -package org.yorkxin.copyasmarkdown.e2e; +package org.yorkxin.copyasmarkdown.e2e.popup; import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebElement; diff --git a/e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/popup/SimpleTest.java b/e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/popup/SimpleTest.java new file mode 100644 index 0000000..b4516a6 --- /dev/null +++ b/e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/popup/SimpleTest.java @@ -0,0 +1,34 @@ +package org.yorkxin.copyasmarkdown.e2e.popup; + +import org.testng.Assert; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; +import org.yorkxin.copyasmarkdown.e2e.DemoPageData; + +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.io.IOException; + +import static org.testng.Assert.assertEquals; + +public class SimpleTest extends BaseTest { + @BeforeMethod + public void setUp() { + DemoPageData dpd = openDemoTabs(false); + openPopupWindow(dpd); + } + + @Test + public void counter() throws IOException, UnsupportedFlavorException { + Assert.assertEquals("6", popupPage.counterAll.getText()); + Assert.assertEquals("3", popupPage.counterHighlighted.getText()); + } + + @Test + public void currentTabLink() throws IOException, UnsupportedFlavorException, InterruptedException { + popupPage.currentTabLinkButton.click(); + Thread.sleep(1000); + String expected = "[Page 0 - Copy as Markdown](http://localhost:5566/0.html)"; + assertEquals(expected, clipboard.getData(DataFlavor.stringFlavor)); + } +} diff --git a/e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/popup/TabExportingTest.java b/e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/popup/TabExportingTest.java new file mode 100644 index 0000000..78400c9 --- /dev/null +++ b/e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/popup/TabExportingTest.java @@ -0,0 +1,119 @@ +package org.yorkxin.copyasmarkdown.e2e.popup; + +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; +import org.yorkxin.copyasmarkdown.e2e.DemoPageData; + +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.io.IOException; + +import static org.testng.Assert.assertEquals; + +public class TabExportingTest extends BaseTest { + @BeforeMethod + public void setUp() { + DemoPageData dpd = openDemoTabs(false); + openPopupWindow(dpd); + } + + @Test + public void allTabsAsList() throws IOException, UnsupportedFlavorException, InterruptedException { + popupPage.allTabsListButton.click(); + Thread.sleep(1000); + String expected = """ + - [Page 0 - Copy as Markdown](http://localhost:5566/0.html) + - [Page 1 - Copy as Markdown](http://localhost:5566/1.html) + - [Page 2 - Copy as Markdown](http://localhost:5566/2.html) + - [Page 3 - Copy as Markdown](http://localhost:5566/3.html) + - [Page 4 - Copy as Markdown](http://localhost:5566/4.html) + - [Page 5 - Copy as Markdown](http://localhost:5566/5.html)"""; + assertEquals(clipboard.getData(DataFlavor.stringFlavor),expected); + } + + @Test + public void allTabsAsTaskList() throws IOException, UnsupportedFlavorException, InterruptedException { + popupPage.allTabsTaskListButton.click(); + Thread.sleep(1000); + String expected = """ + - [ ] [Page 0 - Copy as Markdown](http://localhost:5566/0.html) + - [ ] [Page 1 - Copy as Markdown](http://localhost:5566/1.html) + - [ ] [Page 2 - Copy as Markdown](http://localhost:5566/2.html) + - [ ] [Page 3 - Copy as Markdown](http://localhost:5566/3.html) + - [ ] [Page 4 - Copy as Markdown](http://localhost:5566/4.html) + - [ ] [Page 5 - Copy as Markdown](http://localhost:5566/5.html)"""; + assertEquals(clipboard.getData(DataFlavor.stringFlavor),expected); + } + + @Test + public void allTabsAsTitleList() throws IOException, UnsupportedFlavorException, InterruptedException { + popupPage.allTabsTitleButton.click(); + Thread.sleep(1000); + String expected = """ + - Page 0 - Copy as Markdown + - Page 1 - Copy as Markdown + - Page 2 - Copy as Markdown + - Page 3 - Copy as Markdown + - Page 4 - Copy as Markdown + - Page 5 - Copy as Markdown"""; + assertEquals(clipboard.getData(DataFlavor.stringFlavor),expected); + } + + @Test + public void allTabsAsUrlList() throws IOException, UnsupportedFlavorException, InterruptedException { + popupPage.allTabsUrlButton.click(); + Thread.sleep(1000); + String expected = """ + - http://localhost:5566/0.html + - http://localhost:5566/1.html + - http://localhost:5566/2.html + - http://localhost:5566/3.html + - http://localhost:5566/4.html + - http://localhost:5566/5.html"""; + assertEquals(clipboard.getData(DataFlavor.stringFlavor),expected); + } + + @Test + public void highlightedTabsAsList() throws IOException, UnsupportedFlavorException, InterruptedException { + popupPage.highlightedTabsListButton.click(); + Thread.sleep(1000); + String expected = """ + - [Page 0 - Copy as Markdown](http://localhost:5566/0.html) + - [Page 2 - Copy as Markdown](http://localhost:5566/2.html) + - [Page 4 - Copy as Markdown](http://localhost:5566/4.html)"""; + assertEquals(clipboard.getData(DataFlavor.stringFlavor),expected); + } + + @Test + public void highlightedTabsAsTaskList() throws IOException, UnsupportedFlavorException, InterruptedException { + popupPage.highlightedTabsTaskListButton.click(); + Thread.sleep(1000); + String expected = """ + - [ ] [Page 0 - Copy as Markdown](http://localhost:5566/0.html) + - [ ] [Page 2 - Copy as Markdown](http://localhost:5566/2.html) + - [ ] [Page 4 - Copy as Markdown](http://localhost:5566/4.html)"""; + assertEquals(clipboard.getData(DataFlavor.stringFlavor),expected); + } + + @Test + public void highlightedTabsAsTitleList() throws IOException, UnsupportedFlavorException, InterruptedException { + popupPage.highlightedTabsTitleButton.click(); + Thread.sleep(1000); + String expected = """ + - Page 0 - Copy as Markdown + - Page 2 - Copy as Markdown + - Page 4 - Copy as Markdown"""; + assertEquals(clipboard.getData(DataFlavor.stringFlavor),expected); + } + + @Test + public void highlightedTabsAsUrlList() throws IOException, UnsupportedFlavorException, InterruptedException { + popupPage.highlightedTabsUrlButton.click(); + Thread.sleep(1000); + String expected = """ + - http://localhost:5566/0.html + - http://localhost:5566/2.html + - http://localhost:5566/4.html"""; + assertEquals(clipboard.getData(DataFlavor.stringFlavor),expected); + } +} diff --git a/e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/PopupPageTest.java b/e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/popup/TabExportingWithGroupsTest.java similarity index 70% rename from e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/PopupPageTest.java rename to e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/popup/TabExportingWithGroupsTest.java index 6b29567..6abab70 100644 --- a/e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/PopupPageTest.java +++ b/e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/popup/TabExportingWithGroupsTest.java @@ -1,52 +1,20 @@ -package org.yorkxin.copyasmarkdown.e2e; +package org.yorkxin.copyasmarkdown.e2e.popup; import org.openqa.selenium.By; -import org.openqa.selenium.WindowType; import org.testng.annotations.*; +import org.yorkxin.copyasmarkdown.e2e.DemoPageData; + import static org.testng.Assert.*; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.UnsupportedFlavorException; import java.io.IOException; -public class PopupPageTest extends BaseTest { - private PopupPage popupPage; - private String popupHandle; - +public class TabExportingWithGroupsTest extends org.yorkxin.copyasmarkdown.e2e.popup.BaseTest { @BeforeMethod - public void openPopupWindow() { - openDemoTabs(); - - String demoWindowId = driver.findElement(By.id("window-id")).getAttribute("value"); - String tab0Id = driver.findElement(By.id("tab-0-id")).getAttribute("value"); - - // Open popup - driver.switchTo().newWindow(WindowType.WINDOW) - .get("chrome-extension://"+extId+"/dist/ui/popup.html?window="+demoWindowId+"&tab="+tab0Id+"&keep_open=1"); - - popupHandle = driver.getWindowHandle(); - popupPage = new PopupPage(driver); - } - - @AfterMethod - public void closePopupWindow() { - driver.switchTo().window(popupHandle).close(); - driver.switchTo().window(mainWindowHandle); - driver.findElement(By.id("close-demo")).click(); - } - - @Test - public void counter() throws IOException, UnsupportedFlavorException { - assertEquals("6", popupPage.counterAll.getText()); - assertEquals("3", popupPage.counterHighlighted.getText()); - } - - @Test - public void currentTabLink() throws IOException, UnsupportedFlavorException, InterruptedException { - popupPage.currentTabLinkButton.click(); - Thread.sleep(1000); - String expected = "[Page 0 - Copy as Markdown](http://localhost:5566/0.html)"; - assertEquals(expected,clipboard.getData(DataFlavor.stringFlavor)); + public void setUp() { + DemoPageData dpd = openDemoTabs(true); + openPopupWindow(dpd); } @Test @@ -62,7 +30,7 @@ public void allTabsAsList() throws IOException, UnsupportedFlavorException, Inte - Untitled green group - [Page 4 - Copy as Markdown](http://localhost:5566/4.html) - [Page 5 - Copy as Markdown](http://localhost:5566/5.html)"""; - assertEquals(expected,clipboard.getData(DataFlavor.stringFlavor)); + assertEquals(clipboard.getData(DataFlavor.stringFlavor),expected); } @Test @@ -78,7 +46,7 @@ public void allTabsAsTaskList() throws IOException, UnsupportedFlavorException, - [ ] Untitled green group - [ ] [Page 4 - Copy as Markdown](http://localhost:5566/4.html) - [ ] [Page 5 - Copy as Markdown](http://localhost:5566/5.html)"""; - assertEquals(expected,clipboard.getData(DataFlavor.stringFlavor)); + assertEquals(clipboard.getData(DataFlavor.stringFlavor),expected); } @Test @@ -94,7 +62,7 @@ public void allTabsAsTitleList() throws IOException, UnsupportedFlavorException, - Untitled green group - Page 4 - Copy as Markdown - Page 5 - Copy as Markdown"""; - assertEquals(expected,clipboard.getData(DataFlavor.stringFlavor)); + assertEquals(clipboard.getData(DataFlavor.stringFlavor),expected); } @Test @@ -110,7 +78,7 @@ public void allTabsAsUrlList() throws IOException, UnsupportedFlavorException, I - Untitled green group - http://localhost:5566/4.html - http://localhost:5566/5.html"""; - assertEquals(expected,clipboard.getData(DataFlavor.stringFlavor)); + assertEquals(clipboard.getData(DataFlavor.stringFlavor),expected); } @Test @@ -123,7 +91,7 @@ public void highlightedTabsAsList() throws IOException, UnsupportedFlavorExcepti - [Page 2 - Copy as Markdown](http://localhost:5566/2.html) - Untitled green group - [Page 4 - Copy as Markdown](http://localhost:5566/4.html)"""; - assertEquals(expected,clipboard.getData(DataFlavor.stringFlavor)); + assertEquals(clipboard.getData(DataFlavor.stringFlavor),expected); } @Test @@ -136,7 +104,7 @@ public void highlightedTabsAsTaskList() throws IOException, UnsupportedFlavorExc - [ ] [Page 2 - Copy as Markdown](http://localhost:5566/2.html) - [ ] Untitled green group - [ ] [Page 4 - Copy as Markdown](http://localhost:5566/4.html)"""; - assertEquals(expected,clipboard.getData(DataFlavor.stringFlavor)); + assertEquals(clipboard.getData(DataFlavor.stringFlavor),expected); } @Test @@ -149,7 +117,7 @@ public void highlightedTabsAsTitleList() throws IOException, UnsupportedFlavorEx - Page 2 - Copy as Markdown - Untitled green group - Page 4 - Copy as Markdown"""; - assertEquals(expected,clipboard.getData(DataFlavor.stringFlavor)); + assertEquals(clipboard.getData(DataFlavor.stringFlavor),expected); } @Test @@ -162,6 +130,6 @@ public void highlightedTabsAsUrlList() throws IOException, UnsupportedFlavorExce - http://localhost:5566/2.html - Untitled green group - http://localhost:5566/4.html"""; - assertEquals(expected,clipboard.getData(DataFlavor.stringFlavor)); + assertEquals(clipboard.getData(DataFlavor.stringFlavor),expected); } } diff --git a/e2e/support/e2e-test-extension/main.html b/e2e/support/e2e-test-extension/main.html index 6395862..603e629 100644 --- a/e2e/support/e2e-test-extension/main.html +++ b/e2e/support/e2e-test-extension/main.html @@ -12,6 +12,7 @@

Copy as Markdown E2E Test Main

+
diff --git a/e2e/support/e2e-test-extension/main.js b/e2e/support/e2e-test-extension/main.js index 282184a..17aed71 100644 --- a/e2e/support/e2e-test-extension/main.js +++ b/e2e/support/e2e-test-extension/main.js @@ -13,7 +13,15 @@ document.querySelector('#open-demo').addEventListener('click', async () => { const winDemo = await chrome.windows.create({ url: urls }); document.querySelector('#window-id').value = winDemo.id; document.querySelector('#tab-0-id').value = winDemo.tabs[0].id; +}); +document.querySelector('#group-tabs').addEventListener('click', async () => { + const windowId = document.querySelector('#window-id').value; + if (windowId === '') { + return; + } + + const winDemo = await chrome.windows.get(parseInt(windowId, 10), { populate: true }); const group1 = await chrome.tabs.group({ tabIds: [winDemo.tabs[1].id, winDemo.tabs[2].id], createProperties: { windowId: winDemo.id }, diff --git a/e2e/support/firefox-e2e-test-extension/README.md b/e2e/support/firefox-e2e-test-extension/README.md new file mode 100644 index 0000000..eee8f97 --- /dev/null +++ b/e2e/support/firefox-e2e-test-extension/README.md @@ -0,0 +1,5 @@ +# Firefox E2E Test Extension + +This extension has almost the same features as `../e2e-test-extension` +except that codes are modified for Firefox compatibility (`manifest.json` +and API differences). diff --git a/e2e/support/firefox-e2e-test-extension/background.js b/e2e/support/firefox-e2e-test-extension/background.js new file mode 100644 index 0000000..21bbd1d --- /dev/null +++ b/e2e/support/firefox-e2e-test-extension/background.js @@ -0,0 +1,16 @@ +async function openMain() { + return browser.windows.create({ url: `moz-extension://${browser.runtime.id}/main.html`}); +} + +browser.action.onClicked.addListener(openMain); + +browser.commands.onCommand.addListener((command) => { + switch (command) { + case "open": + openMain().then(() => console.log("ok")); + break; + default: + browser.runtime.lastError = new Error(`invalid command: ${command}`); + break; + } +}); diff --git a/e2e/support/firefox-e2e-test-extension/icon.png b/e2e/support/firefox-e2e-test-extension/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..2c57afca12ae05560290faaea916e8c7aab45a14 GIT binary patch literal 16613 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 literal 0 HcmV?d00001 diff --git a/e2e/support/firefox-e2e-test-extension/main.html b/e2e/support/firefox-e2e-test-extension/main.html new file mode 100644 index 0000000..a37b965 --- /dev/null +++ b/e2e/support/firefox-e2e-test-extension/main.html @@ -0,0 +1,23 @@ + + + + + Copy as Markdown - Demo + + +

Copy as Markdown E2E Test Main (Firefox)

+ +
+ +
+
+ +
+ +
+
+ + + + + \ No newline at end of file diff --git a/e2e/support/firefox-e2e-test-extension/main.js b/e2e/support/firefox-e2e-test-extension/main.js new file mode 100644 index 0000000..fb936af --- /dev/null +++ b/e2e/support/firefox-e2e-test-extension/main.js @@ -0,0 +1,49 @@ +let baseUrl = 'http://localhost:5566'; + +const URL_PARAMS = new URLSearchParams(window.location.search); + +if (URL_PARAMS.has('base_url')) { + baseUrl = URL_PARAMS.get('base_url'); +} + +document.querySelector('#open-demo').addEventListener('click', async () => { + const urls = [0, 1, 2, 3, 4, 5] + .map((i) => `${baseUrl}/${i}.html`); + + const winDemo = await browser.windows.create({ url: urls }); + document.querySelector('#window-id').value = winDemo.id; + document.querySelector('#tab-0-id').value = winDemo.tabs[0].id; +}); + +document.querySelector('#highlight-tabs').addEventListener('click', async () => { + const windowId = document.querySelector('#window-id').value; + if (windowId === '') { + return; + } + + const winDemo = await browser.windows.get(parseInt(windowId, 10), { populate: true }); + + await browser.tabs.update(winDemo.tabs[0].id, { highlighted: true }); + await browser.tabs.update(winDemo.tabs[2].id, { highlighted: true }); + await browser.tabs.update(winDemo.tabs[4].id, { highlighted: true }); +}); + +document.querySelector('#switch-to-demo').addEventListener('click', async () => { + const windowId = document.querySelector('#window-id').value; + if (windowId === '') { + return; + } + + await browser.windows.update(parseInt(windowId, 10), { focused: true }); +}); + +document.querySelector('#close-demo').addEventListener('click', async () => { + const windowId = document.querySelector('#window-id').value; + if (windowId === '') { + return; + } + + await browser.windows.remove(parseInt(windowId, 10)); + document.querySelector('#window-id').value = ''; + document.querySelector('#tab-0-id').value = ''; +}); diff --git a/e2e/support/firefox-e2e-test-extension/manifest.json b/e2e/support/firefox-e2e-test-extension/manifest.json new file mode 100644 index 0000000..fc50e5d --- /dev/null +++ b/e2e/support/firefox-e2e-test-extension/manifest.json @@ -0,0 +1,24 @@ +{ + "name": "Copy as Markdown E2E Test", + "version": "0.0.1", + "manifest_version": 3, + "description": "E2E Test no tame no Extension", + "permissions": [ + "tabs" + ], + "background": { + "scripts": ["background.js"] + }, + "action": { + "default_title": "Open E2E Test Main Page" + }, + "commands": { + "open": { + "description": "Open E2E Test Main Page", + "suggested_key": { + "default": "Ctrl+Shift+Z", + "mac": "MacCtrl+Shift+Z" + } + } + } +} diff --git a/e2e/testng-chrome.xml b/e2e/testng-chrome.xml new file mode 100644 index 0000000..6e77102 --- /dev/null +++ b/e2e/testng-chrome.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/e2e/testng-firefox.xml b/e2e/testng-firefox.xml new file mode 100644 index 0000000..25cf8f4 --- /dev/null +++ b/e2e/testng-firefox.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/e2e/testng.xml b/e2e/testng.xml new file mode 100644 index 0000000..5d78a43 --- /dev/null +++ b/e2e/testng.xml @@ -0,0 +1,9 @@ + + + + + + + + + From 374a520d0556a898c38a4e5125649f170eed351d Mon Sep 17 00:00:00 2001 From: Yucheng Chuang Date: Tue, 30 Apr 2024 22:10:04 +0800 Subject: [PATCH 4/4] update readme --- e2e/README.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/e2e/README.md b/e2e/README.md index 839b18f..cabc841 100644 --- a/e2e/README.md +++ b/e2e/README.md @@ -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 @@ -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. @@ -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-.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 \ No newline at end of file +* Test Edge +* Test on Linux & Windows \ No newline at end of file