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 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..8ff34ec 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,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; @@ -23,13 +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) {} - -@TestInstance(TestInstance.Lifecycle.PER_CLASS) public class BaseTest { + protected String browser; protected WebDriver driver; private HttpServer server; protected String extId; @@ -37,18 +38,15 @@ public class BaseTest { protected String mainWindowHandle; protected String demoWindowHandle; protected Clipboard clipboard; - List 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"); @@ -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; @@ -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 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); @@ -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); } } 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..843d1dd 100644 --- a/e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/ContextMenuTest.java +++ b/e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/ContextMenuTest.java @@ -1,21 +1,19 @@ 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; import java.awt.datatransfer.UnsupportedFlavorException; import java.awt.event.KeyEvent; import java.io.IOException; - -import static org.junit.jupiter.api.Assertions.assertEquals; +import java.util.Objects; public class ContextMenuTest extends BaseTest { - @BeforeEach + @BeforeMethod public void goToQaPage() { driver.get("http://localhost:5566/qa.html"); } @@ -26,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); @@ -38,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 @@ -47,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); @@ -58,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 @@ -70,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); @@ -96,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 @@ -105,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); @@ -116,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 @@ -132,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); @@ -141,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 0b11902..0000000 --- a/e2e/src/test/java/org/yorkxin/copyasmarkdown/e2e/KeyboardShortcutTest.java +++ /dev/null @@ -1,283 +0,0 @@ -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 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; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -public class KeyboardShortcutTest extends BaseTest { - @BeforeAll - 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)); - } - - @Nested - @DisplayName("Tab Exporting") - class TabExportingCases { - @BeforeEach - public void setUp() { - openDemoTabs(); - driver.findElement(By.id("switch-to-demo")).click(); - } - - @AfterEach - 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 68% 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 b52018f..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,54 +1,20 @@ -package org.yorkxin.copyasmarkdown.e2e; +package org.yorkxin.copyasmarkdown.e2e.popup; -import org.junit.jupiter.api.*; 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.*; 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 - 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); - } - - @AfterEach - 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 class TabExportingWithGroupsTest extends org.yorkxin.copyasmarkdown.e2e.popup.BaseTest { + @BeforeMethod + public void setUp() { + DemoPageData dpd = openDemoTabs(true); + openPopupWindow(dpd); } @Test @@ -64,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 @@ -80,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 @@ -96,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 @@ -112,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 @@ -125,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 @@ -138,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 @@ -151,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 @@ -164,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 0000000..2c57afc Binary files /dev/null and b/e2e/support/firefox-e2e-test-extension/icon.png differ 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 @@ + + + + + + + + + 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'], +});