aboutsummaryrefslogtreecommitdiff
path: root/e2e/lib
diff options
context:
space:
mode:
Diffstat (limited to 'e2e/lib')
-rw-r--r--e2e/lib/Console.js41
-rw-r--r--e2e/lib/Console.ts72
-rw-r--r--e2e/lib/FormOptionPage.ts68
-rw-r--r--e2e/lib/JSONOptionPage.ts22
-rw-r--r--e2e/lib/OptionPage.ts39
-rw-r--r--e2e/lib/Page.ts93
-rw-r--r--e2e/lib/TestServer.ts64
-rw-r--r--e2e/lib/clipboard.js63
-rw-r--r--e2e/lib/clipboard.ts107
9 files changed, 465 insertions, 104 deletions
diff --git a/e2e/lib/Console.js b/e2e/lib/Console.js
deleted file mode 100644
index 3a39b64..0000000
--- a/e2e/lib/Console.js
+++ /dev/null
@@ -1,41 +0,0 @@
-class Console {
- constructor(session) {
- this.session = session;
- }
-
- async sendKeys(...keys) {
- let input = await this.session.findElementByCSS('input');
- input.sendKeys(...keys);
- }
-
- async currentValue() {
- return await this.session.executeScript(() => {
- let input = document.querySelector('input');
- return input.value;
- });
- }
-
- async getCompletions() {
- return await this.session.executeScript(() => {
- let items = document.querySelectorAll('.vimvixen-console-completion > li');
- if (items.length === 0) {
- throw new Error('completion items not found');
- }
-
- let objs = [];
- for (let li of items) {
- if (li.classList.contains('vimvixen-console-completion-title')) {
- objs.push({ type: 'title', text: li.textContent.trim() });
- } else if ('vimvixen-console-completion-item') {
- let highlight = li.classList.contains('vimvixen-completion-selected');
- objs.push({ type: 'item', text: li.textContent.trim(), highlight });
- } else {
- throw new Error(`unexpected class: ${li.className}`);
- }
- }
- return objs;
- });
- }
-}
-
-module.exports = Console;
diff --git a/e2e/lib/Console.ts b/e2e/lib/Console.ts
new file mode 100644
index 0000000..233bf48
--- /dev/null
+++ b/e2e/lib/Console.ts
@@ -0,0 +1,72 @@
+import { WebDriver, By, Key } from 'selenium-webdriver';
+
+export type CompletionItem = {
+ type: string;
+ text: string;
+ highlight: boolean;
+}
+
+export class Console {
+ constructor(private webdriver: WebDriver) {
+ }
+
+ async sendKeys(...keys: string[]) {
+ let input = await this.webdriver.findElement(By.css('input'));
+ input.sendKeys(...keys);
+ }
+
+ async currentValue() {
+ return await this.webdriver.executeScript(() => {
+ let input = document.querySelector('input');
+ if (input === null) {
+ throw new Error('could not find input element');
+ }
+ return input.value;
+ });
+ }
+
+ async execCommand(command: string): Promise<void> {
+ let input = await this.webdriver.findElement(By.css('input.vimvixen-console-command-input'));
+ await input.sendKeys(command, Key.ENTER);
+ }
+
+ async getErrorMessage(): Promise<string> {
+ let p = await this.webdriver.findElement(By.css('.vimvixen-console-error'));
+ return p.getText();
+ }
+
+ async inputKeys(...keys: string[]) {
+ let input = await this.webdriver.findElement(By.css('input'));
+ await input.sendKeys(...keys);
+ }
+
+ getCompletions(): Promise<CompletionItem[]> {
+ return this.webdriver.executeScript(() => {
+ let items = document.querySelectorAll('.vimvixen-console-completion > li');
+ if (items.length === 0) {
+ throw new Error('completion items not found');
+ }
+
+ let objs = [];
+ for (let li of Array.from(items)) {
+ if (li.classList.contains('vimvixen-console-completion-title')) {
+ objs.push({ type: 'title', text: li.textContent!!.trim() });
+ } else if ('vimvixen-console-completion-item') {
+ let highlight = li.classList.contains('vimvixen-completion-selected');
+ objs.push({ type: 'item', text: li.textContent!!.trim(), highlight });
+ } else {
+ throw new Error(`unexpected class: ${li.className}`);
+ }
+ }
+ return objs;
+ });
+ }
+
+ async close(): Promise<void> {
+ let input = await this.webdriver.findElement(By.css('input'));
+ await input.sendKeys(Key.ESCAPE);
+ // TODO remove sleep
+ await new Promise(resolve => setTimeout(resolve, 100));
+ await (this.webdriver.switchTo() as any).parentFrame();
+ }
+}
diff --git a/e2e/lib/FormOptionPage.ts b/e2e/lib/FormOptionPage.ts
new file mode 100644
index 0000000..c49a44f
--- /dev/null
+++ b/e2e/lib/FormOptionPage.ts
@@ -0,0 +1,68 @@
+import { Lanthan } from 'lanthan';
+import { WebDriver, By, until } from 'selenium-webdriver';
+
+export default class FormOptionPage {
+ private webdriver: WebDriver;
+
+ constructor(lanthan: Lanthan) {
+ this.webdriver = lanthan.getWebDriver();
+ }
+
+ async setBlacklist(nth: number, value: string): Promise<void> {
+ let selector = '.form-blacklist-form-row > .column-url';
+ let inputs = await this.webdriver.findElements(By.css(selector));
+ if (inputs.length <= nth) {
+ throw new RangeError('Index out of range to set a blacklist')
+ }
+ await inputs[nth].sendKeys(value);
+ await this.webdriver.executeScript(`document.querySelectorAll('${selector}')[${nth}].blur()`);
+ }
+
+ async setSearchEngine(nth: number, name: string, url: string) {
+ let selector = '.form-search-form-row > .column-name';
+ let inputs = await this.webdriver.findElements(By.css(selector));
+ if (inputs.length <= nth) {
+ throw new RangeError('Index out of range to set a search engine')
+ }
+ await inputs[nth].sendKeys(name);
+ await this.webdriver.executeScript(`document.querySelectorAll('${selector}')[${nth}].blur()`);
+
+ selector = '.form-search-form-row > .column-url';
+ inputs = await this.webdriver.findElements(By.css(selector));
+ if (inputs.length <= nth) {
+ throw new RangeError('Index out of range to set a search engine')
+ }
+ await inputs[nth].sendKeys(url);
+ await this.webdriver.executeScript(`document.querySelectorAll('${selector}')[${nth}].blur()`);
+ }
+
+ async addBlacklist(): Promise<void> {
+ let rows = await this.webdriver.findElements(By.css(`.form-blacklist-form-row`));
+ let button = await this.webdriver.findElement(By.css('.form-blacklist-form .ui-add-button'))
+ await button.click();
+ await this.webdriver.wait(until.elementLocated(By.css(`.form-blacklist-form-row:nth-child(${rows.length + 1})`)));
+ }
+
+ async removeBlackList(nth: number): Promise<void> {
+ let buttons = await this.webdriver.findElements(By.css('.form-blacklist-form-row .ui-delete-button'));
+ if (buttons.length <= nth) {
+ throw new RangeError('Index out of range to remove blacklist')
+ }
+ await buttons[nth].click()
+ }
+
+ async addSearchEngine(): Promise<void> {
+ let rows = await this.webdriver.findElements(By.css(`.form-search-form-row > .column-name`));
+ let button = await this.webdriver.findElement(By.css('.form-search-form > .ui-add-button'))
+ await button.click();
+ await this.webdriver.wait(until.elementLocated(By.css(`.form-search-form-row:nth-child(${rows.length + 1})`)));
+ }
+
+ async setDefaultSearchEngine(nth: number): Promise<void> {
+ let radios = await this.webdriver.findElements(By.css('.form-search-form-row input[type=radio]'));
+ if (radios.length <= nth) {
+ throw new RangeError('Index out of range to set a default search engine');
+ }
+ await radios[nth].click();
+ }
+}
diff --git a/e2e/lib/JSONOptionPage.ts b/e2e/lib/JSONOptionPage.ts
new file mode 100644
index 0000000..ac1ae3d
--- /dev/null
+++ b/e2e/lib/JSONOptionPage.ts
@@ -0,0 +1,22 @@
+import { Lanthan } from 'lanthan';
+import { WebDriver, By } from 'selenium-webdriver';
+
+export default class JSONOptionPage {
+ private webdriver: WebDriver;
+
+ constructor(lanthan: Lanthan) {
+ this.webdriver = lanthan.getWebDriver();
+ }
+
+ async updateSettings(value: string): Promise<void> {
+ let textarea = await this.webdriver.findElement(By.css('textarea'));
+ await this.webdriver.executeScript(`document.querySelector('textarea').value = '${value}'`)
+ await textarea.sendKeys(' ');
+ await this.webdriver.executeScript(() => document.querySelector('textarea')!!.blur());
+ }
+
+ async getErrorMessage(): Promise<string> {
+ let error = await this.webdriver.findElement(By.css('.settings-ui-input-error'));
+ return error.getText();
+ }
+}
diff --git a/e2e/lib/OptionPage.ts b/e2e/lib/OptionPage.ts
new file mode 100644
index 0000000..c183b06
--- /dev/null
+++ b/e2e/lib/OptionPage.ts
@@ -0,0 +1,39 @@
+import { Lanthan } from 'lanthan';
+import { WebDriver, By } from 'selenium-webdriver';
+import JSONOptionPage from './JSONOptionPage';
+import FormOptionPage from './FormOptionPage';
+
+export default class OptionPage {
+ private webdriver: WebDriver;
+
+ constructor(private lanthan: Lanthan) {
+ this.webdriver = lanthan.getWebDriver();
+ }
+
+ static async open(lanthan: Lanthan) {
+ let url = await lanthan.getWebExtBrowser().runtime.getURL("build/settings.html")
+ await lanthan.getWebDriver().navigate().to(url);
+ return new OptionPage(lanthan);
+ }
+
+ async switchToForm(): Promise<FormOptionPage> {
+ let useFormInput = await this.webdriver.findElement(By.css('#setting-source-form'));
+ await useFormInput.click();
+ await this.webdriver.switchTo().alert().accept();
+ return new FormOptionPage(this.lanthan);
+ }
+
+ async asFormOptionPage(): Promise<FormOptionPage> {
+ // TODO validate current page
+ return new FormOptionPage(this.lanthan);
+ }
+
+ async asJSONOptionPage(): Promise<JSONOptionPage> {
+ // TODO validate current page
+ return new JSONOptionPage(this.lanthan);
+ }
+
+ scrollTo(x: number, y: number): Promise<void> {
+ return this.webdriver.executeScript(`window.scrollTo(${x}, ${y})`);
+ }
+}
diff --git a/e2e/lib/Page.ts b/e2e/lib/Page.ts
new file mode 100644
index 0000000..7a5dd7a
--- /dev/null
+++ b/e2e/lib/Page.ts
@@ -0,0 +1,93 @@
+import { WebDriver, By, until } from 'selenium-webdriver';
+import { Console } from './Console';
+
+type Hint = {
+ displayed: boolean,
+ text: string,
+};
+
+export default class Page {
+ private constructor(private webdriver: WebDriver) {
+ }
+
+ static async currentContext(webdriver: WebDriver): Promise<Page> {
+ await Page.waitForConsoleLoaded(webdriver);
+ return new Page(webdriver);
+ }
+
+ static async navigateTo(webdriver: WebDriver, url: string): Promise<Page> {
+ await webdriver.navigate().to(url);
+ await Page.waitForConsoleLoaded(webdriver);
+ return new Page(webdriver);
+ }
+
+ async sendKeys(...keys: Array<string|number|Promise<string|number>>): Promise<void> {
+ let body = await this.webdriver.findElement(By.css('body'));
+ await body.sendKeys(...keys);
+ }
+
+ async navigateTo(url: string): Promise<Page> {
+ await this.webdriver.navigate().to(url);
+ await Page.waitForConsoleLoaded(this.webdriver);
+ return new Page(this.webdriver);
+ }
+
+ async showConsole(): Promise<Console> {
+ let iframe = this.webdriver.findElement(By.css('#vimvixen-console-frame'));
+
+ await this.sendKeys(':');
+ await this.webdriver.wait(until.elementIsVisible(iframe));
+ await this.webdriver.switchTo().frame(0);
+ await this.webdriver.wait(until.elementLocated(By.css('input.vimvixen-console-command-input')));
+ return new Console(this.webdriver);
+ }
+
+ async getConsole(): Promise<Console> {
+ let iframe = this.webdriver.findElement(By.css('#vimvixen-console-frame'));
+
+ await this.webdriver.wait(until.elementIsVisible(iframe));
+ await this.webdriver.switchTo().frame(0);
+ return new Console(this.webdriver);
+ }
+
+ async getScrollX(): Promise<number> {
+ return await this.webdriver.executeScript(() => window.pageXOffset);
+ }
+
+ getScrollY(): Promise<number> {
+ return this.webdriver.executeScript(() => window.pageYOffset);
+ }
+
+ scrollTo(x: number, y: number): Promise<void> {
+ return this.webdriver.executeScript(`window.scrollTo(${x}, ${y})`);
+ }
+
+ pageHeight(): Promise<number> {
+ return this.webdriver.executeScript(() => window.document.documentElement.clientHeight);
+ }
+
+ async waitAndGetHints(): Promise<Hint[]> {
+ await this.webdriver.wait(until.elementsLocated(By.css('.vimvixen-hint')));
+
+ let elements = await this.webdriver.findElements(By.css(`.vimvixen-hint`));
+ let hints = [];
+ for (let e of elements) {
+ let display = await e.getCssValue('display');
+ let text = await e.getText();
+ hints.push({
+ displayed: display !== 'none',
+ text: text,
+ });
+ }
+ return hints;
+ }
+
+ private static async waitForConsoleLoaded(webdriver: WebDriver) {
+ let topFrame = await webdriver.executeScript(() => window.top === window);
+ if (!topFrame) {
+ return;
+ }
+ await webdriver.wait(until.elementLocated(By.css('iframe.vimvixen-console-frame')));
+ await new Promise(resolve => setTimeout(resolve, 100));
+ }
+}
diff --git a/e2e/lib/TestServer.ts b/e2e/lib/TestServer.ts
new file mode 100644
index 0000000..c010e37
--- /dev/null
+++ b/e2e/lib/TestServer.ts
@@ -0,0 +1,64 @@
+import * as http from 'http';
+import * as net from 'net'
+import express from 'express';
+
+type HandlerFunc = (req: express.Request, res: express.Response) => void;
+
+export default class TestServer {
+ private http?: http.Server;
+
+ private app: express.Application;
+
+ constructor(
+ private port = 0,
+ private address = '127.0.0.1',
+ ){
+ this.app = express();
+ }
+
+ handle(path: string, f: HandlerFunc): TestServer {
+ this.app.get(path, f);
+ return this;
+ }
+
+ receiveContent(path: string, content: string): TestServer {
+ this.app.get(path, (_req: express.Request, res: express.Response) => {
+ res.status(200).send(content)
+ });
+ return this;
+ }
+
+ url(path: string = '/'): string {
+ if (!this.http) {
+ throw new Error('http server not started');
+ }
+
+ let addr = this.http.address() as net.AddressInfo;
+ return `http://${addr.address}:${addr.port}${path}`
+ }
+
+ start(): Promise<void> {
+ if (this.http) {
+ throw new Error('http server already started');
+ }
+
+ this.http = http.createServer(this.app)
+ return new Promise((resolve) => {
+ this.http!!.listen(this.port, this.address, () => {
+ resolve();
+ })
+ });
+ }
+
+ stop(): Promise<void> {
+ if (!this.http) {
+ return Promise.resolve();
+ }
+ return new Promise((resolve) => {
+ this.http!!.close(() => {
+ this.http = undefined;
+ resolve();
+ });
+ })
+ }
+}
diff --git a/e2e/lib/clipboard.js b/e2e/lib/clipboard.js
deleted file mode 100644
index 4061dbd..0000000
--- a/e2e/lib/clipboard.js
+++ /dev/null
@@ -1,63 +0,0 @@
-'use strict';
-
-const { spawn } = require('child_process');
-
-const readLinux = () => {
- let stdout = '', stderr = '';
- return new Promise((resolve, reject) => {
- let xsel = spawn('xsel', ['--clipboard', '--output']);
- xsel.stdout.on('data', (data) => {
- stdout += data;
- });
- xsel.stderr.on('data', (data) => {
- stderr += data;
- });
- xsel.on('close', (code) => {
- if (code !== 0) {
- throw new Error(`xsel returns ${code}: ${stderr}`)
- }
- resolve(stdout);
- });
- });
-};
-
-const writeLinux = (data) => {
- let stdout = '', stderr = '';
- return new Promise((resolve, reject) => {
- let xsel = spawn('xsel', ['--clipboard', '--input']);
- xsel.stderr.on('data', (data) => {
- stderr += data;
- });
- xsel.on('close', (code) => {
- if (code !== 0) {
- throw new Error(`xsel returns ${code}: ${stderr}`)
- }
- resolve();
- });
- xsel.stdin.write(data);
- xsel.stdin.end();
- });
-};
-
-const unsupported = (os) => {
- return () => {
- throw new Error(`Unsupported os: ${os}`);
- };
-};
-
-const detect = () => {
- switch (process.platform) {
- case 'linux':
- return {
- read: readLinux,
- write: writeLinux,
- };
- default:
- return {
- read: unsupported(process.platform),
- write: unsupported(process.platform),
- };
- }
-}
-
-module.exports = detect();
diff --git a/e2e/lib/clipboard.ts b/e2e/lib/clipboard.ts
new file mode 100644
index 0000000..c1eddbb
--- /dev/null
+++ b/e2e/lib/clipboard.ts
@@ -0,0 +1,107 @@
+import { spawn } from 'child_process';
+
+const readLinux = (): Promise<string> => {
+ let stdout = '', stderr = '';
+ return new Promise((resolve) => {
+ let xsel = spawn('xsel', ['--clipboard', '--output']);
+ xsel.stdout.on('data', (data) => {
+ stdout += data;
+ });
+ xsel.stderr.on('data', (data) => {
+ stderr += data;
+ });
+ xsel.on('close', (code) => {
+ if (code !== 0) {
+ throw new Error(`xsel returns ${code}: ${stderr}`)
+ }
+ resolve(stdout);
+ });
+ });
+};
+
+const writeLinux = (data: string): Promise<string> => {
+ let stderr = '';
+ return new Promise((resolve) => {
+ let xsel = spawn('xsel', ['--clipboard', '--input']);
+ xsel.stderr.on('data', (data) => {
+ stderr += data;
+ });
+ xsel.on('close', (code) => {
+ if (code !== 0) {
+ throw new Error(`xsel returns ${code}: ${stderr}`)
+ }
+ resolve();
+ });
+ xsel.stdin.write(data);
+ xsel.stdin.end();
+ });
+};
+
+const readDarwin = (): Promise<string> => {
+ let stdout = '', stderr = '';
+ return new Promise((resolve) => {
+ let pbpaste = spawn('pbpaste');
+ pbpaste.stdout.on('data', (data) => {
+ stdout += data;
+ });
+ pbpaste.stderr.on('data', (data) => {
+ stderr += data;
+ });
+ pbpaste.on('close', (code) => {
+ if (code !== 0) {
+ throw new Error(`pbpaste returns ${code}: ${stderr}`)
+ }
+ resolve(stdout);
+ });
+ });
+};
+
+const writeDarwin = (data: string): Promise<string> => {
+ let stderr = '';
+ return new Promise((resolve) => {
+ let pbcopy = spawn('pbcopy');
+ pbcopy.stderr.on('data', (data) => {
+ stderr += data;
+ });
+ pbcopy.on('close', (code) => {
+ if (code !== 0) {
+ throw new Error(`pbcopy returns ${code}: ${stderr}`)
+ }
+ resolve();
+ });
+ pbcopy.stdin.write(data);
+ pbcopy.stdin.end();
+ });
+};
+
+class UnsupportedError extends Error {
+ constructor(platform: string) {
+ super();
+ this.message = `Unsupported platform: ${platform}`;
+ }
+}
+
+const read = () => {
+ switch (process.platform) {
+ case 'linux':
+ return readLinux();
+ case 'darwin':
+ return readDarwin();
+ }
+ throw new UnsupportedError(process.platform);
+}
+
+const write = (data: string) => {
+ switch (process.platform) {
+ case 'linux':
+ return writeLinux(data);
+ case 'darwin':
+ return writeDarwin(data);
+ }
+ throw new UnsupportedError(process.platform);
+}
+
+export {
+ read,
+ write,
+};