2019-04-25 20:01:23 +02:00
|
|
|
require("geckodriver")
|
|
|
|
|
|
|
|
const fs = require("fs")
|
|
|
|
const webdriver = require("selenium-webdriver")
|
|
|
|
const Options = require("selenium-webdriver/firefox").Options
|
|
|
|
|
|
|
|
jest.setTimeout(60000)
|
|
|
|
|
2019-05-10 20:02:03 +02:00
|
|
|
// API docs because I waste too much time looking for them every time I go back to this:
|
|
|
|
// https://seleniumhq.github.io/selenium/docs/api/javascript/
|
|
|
|
|
|
|
|
const vimToSelenium = {
|
|
|
|
"Down": webdriver.Key.ARROW_DOWN,
|
|
|
|
"Left": webdriver.Key.ARROW_LEFT,
|
|
|
|
"Right": webdriver.Key.ARROW_RIGHT,
|
|
|
|
"Up": webdriver.Key.ARROW_UP,
|
|
|
|
"BS": webdriver.Key.BACK_SPACE,
|
|
|
|
"Del": webdriver.Key.DELETE,
|
|
|
|
"End": webdriver.Key.END,
|
|
|
|
"CR": webdriver.Key.ENTER,
|
|
|
|
"Esc": webdriver.Key.ESCAPE,
|
|
|
|
"Home": webdriver.Key.HOME,
|
|
|
|
"PageDown": webdriver.Key.PAGE_DOWN,
|
|
|
|
"PageUp": webdriver.Key.PAGE_UP,
|
|
|
|
"Tab": webdriver.Key.TAB,
|
|
|
|
"lt": "<",
|
|
|
|
}
|
|
|
|
|
|
|
|
const modToSelenium = {
|
|
|
|
"A": webdriver.Key.ALT,
|
|
|
|
"C": webdriver.Key.CONTROL,
|
|
|
|
"M": webdriver.Key.META,
|
|
|
|
"S": webdriver.Key.SHIFT,
|
|
|
|
}
|
|
|
|
|
|
|
|
function translateKeys(string) {
|
|
|
|
return string.replace(/<([^>-]+)-?([^>]*)>/g, (wholematch, modifier, key, pos, str) => {
|
|
|
|
// There only is a modifier if 'key' isn't empty (<Up> = no modifier)
|
|
|
|
if (key.length > 0) {
|
|
|
|
return webdriver.Key.chord(
|
|
|
|
...(modifier.split("").map(m => modToSelenium[m])),
|
|
|
|
(vimToSelenium[key] || key),
|
|
|
|
webdriver.Key.NULL,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
return vimToSelenium[modifier] || modifier
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
function sendKeys (driver, keys) {
|
|
|
|
return translateKeys(keys).split("")
|
|
|
|
.reduce((prom, key) => {
|
|
|
|
return prom.then(_ => driver.wait(driver.switchTo().activeElement()))
|
|
|
|
.then(elem => elem.sendKeys(key))
|
|
|
|
.then(_ => driver.sleep(5))
|
|
|
|
}, Promise.resolve())
|
|
|
|
}
|
|
|
|
|
2019-04-25 20:01:23 +02:00
|
|
|
describe("webdriver", () => {
|
2019-05-12 11:24:29 +02:00
|
|
|
async function getDriver() {
|
2019-04-25 20:01:23 +02:00
|
|
|
const dir = "web-ext-artifacts"
|
|
|
|
const extensionName = "tridactyl.xpi"
|
|
|
|
const extensionPath = dir + "/" + extensionName
|
2019-05-12 11:24:29 +02:00
|
|
|
const driver = new webdriver.Builder()
|
2019-04-25 20:01:23 +02:00
|
|
|
.forBrowser("firefox")
|
|
|
|
.setFirefoxOptions((new Options())
|
|
|
|
.setPreference("xpinstall.signatures.required", false)
|
|
|
|
.addExtensions(extensionPath))
|
|
|
|
.build()
|
|
|
|
// Wait until addon is loaded and :tutor is displayed
|
|
|
|
await driver.wait(webdriver.until.elementLocated(webdriver.By.id("cmdline_iframe")))
|
2019-05-10 20:02:03 +02:00
|
|
|
// And wait a bit more otherwise Tridactyl won't be happy
|
2019-05-12 11:24:29 +02:00
|
|
|
await driver.sleep(500)
|
|
|
|
return driver
|
|
|
|
}
|
2019-04-25 20:01:23 +02:00
|
|
|
|
2019-05-12 11:24:29 +02:00
|
|
|
async function killDriver(driver) {
|
2019-04-25 20:01:23 +02:00
|
|
|
try {
|
|
|
|
await driver.close()
|
|
|
|
} catch(e) {}
|
|
|
|
try {
|
|
|
|
await driver.quit()
|
|
|
|
} catch(e) {}
|
2019-05-12 11:24:29 +02:00
|
|
|
}
|
2019-04-25 20:01:23 +02:00
|
|
|
|
2019-05-12 11:24:29 +02:00
|
|
|
async function untilTabUrlMatches(driver, tabId, pattern) {
|
|
|
|
let match
|
|
|
|
do {
|
|
|
|
match = (await driver.executeScript(`return tri.browserBg.tabs.get(${tabId})`))
|
|
|
|
.url
|
|
|
|
.match(pattern)
|
|
|
|
} while (!match)
|
|
|
|
return match
|
|
|
|
}
|
|
|
|
|
|
|
|
async function newTabWithoutChangingOldTabs (driver, callback) {
|
2019-05-10 20:02:03 +02:00
|
|
|
const tabsBefore = await driver.executeScript("return tri.browserBg.tabs.query({})")
|
2019-05-11 15:26:34 +02:00
|
|
|
const result = await callback(tabsBefore);
|
2019-05-12 11:24:29 +02:00
|
|
|
const tabsAfter = await driver.wait(async () => {
|
|
|
|
let tabsAfter
|
|
|
|
do {
|
|
|
|
tabsAfter = await driver.executeScript("return tri.browserBg.tabs.query({})")
|
|
|
|
} while (tabsAfter.length == tabsBefore.length)
|
|
|
|
return tabsAfter
|
|
|
|
})
|
2019-05-10 20:02:03 +02:00
|
|
|
// A single new tab has been created
|
|
|
|
expect(tabsAfter.length).toBe(tabsBefore.length + 1)
|
|
|
|
|
|
|
|
// None of the previous tabs changed, except maybe for their index
|
2019-05-11 15:26:34 +02:00
|
|
|
const newtab = tabsAfter.find(tab2 => !tabsBefore.find(tab => tab.id == tab2.id))
|
2019-05-10 20:02:03 +02:00
|
|
|
const notNewTabs = tabsAfter.slice()
|
|
|
|
notNewTabs.splice(tabsAfter.findIndex(tab => tab == newtab), 1)
|
|
|
|
const ignoreValues = {
|
2019-05-12 11:24:29 +02:00
|
|
|
active: false, // the previously-active tab isn't necessarily active anymore
|
2019-05-10 20:02:03 +02:00
|
|
|
highlighted: true, // changing tabs changes highlights
|
|
|
|
index: 0, // indexes might not be the same depending on whether the new tab is
|
|
|
|
lastAccessed: 0, // lastAccessed has also changed for the previously-active tab
|
|
|
|
}
|
|
|
|
for (let i = 0; i < tabsBefore.length; ++i) {
|
|
|
|
let copy1 = Object.assign({}, tabsBefore[i], ignoreValues)
|
|
|
|
let copy2 = Object.assign({}, notNewTabs[i], ignoreValues)
|
|
|
|
expect(copy1).toEqual(copy2)
|
|
|
|
}
|
2019-05-11 15:26:34 +02:00
|
|
|
return [newtab, result]
|
|
|
|
}
|
|
|
|
|
|
|
|
test("`:tabopen<CR>` opens the newtab page.", async () => {
|
2019-05-12 11:24:29 +02:00
|
|
|
const driver = await getDriver()
|
|
|
|
return newTabWithoutChangingOldTabs(driver, async (tabsBefore) => {
|
2019-05-11 15:26:34 +02:00
|
|
|
await sendKeys(driver, ":tabopen<CR>")
|
2019-05-12 11:24:29 +02:00
|
|
|
}).then(async ([newtab, _]) => {
|
2019-05-11 15:26:34 +02:00
|
|
|
// The new tab is active
|
|
|
|
expect(newtab.active).toEqual(true)
|
|
|
|
// Its url is the newtab page's url
|
2019-05-12 11:24:29 +02:00
|
|
|
await driver.wait(untilTabUrlMatches(driver, newtab.id, new RegExp("moz-extension://.*/static/newtab.html")), 10000)
|
|
|
|
}).finally(() => killDriver(driver))
|
2019-05-11 15:26:34 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
test("`:tabopen https://example.org<CR>` opens example.org.", async () => {
|
2019-05-12 11:24:29 +02:00
|
|
|
const driver = await getDriver()
|
|
|
|
return newTabWithoutChangingOldTabs(driver, async () => {
|
2019-05-11 15:26:34 +02:00
|
|
|
await sendKeys(driver, ":tabopen https://example.org<CR>")
|
2019-05-12 11:24:29 +02:00
|
|
|
}).then(async ([newtab, _]) => {
|
2019-05-11 15:26:34 +02:00
|
|
|
// The new tab is active
|
|
|
|
expect(newtab.active).toEqual(true)
|
|
|
|
// Its url is example.org
|
2019-05-12 11:24:29 +02:00
|
|
|
await driver.wait(untilTabUrlMatches(driver, newtab.id, "https://example.org"), 10000)
|
|
|
|
}).finally(() => killDriver(driver))
|
|
|
|
})
|
|
|
|
|
|
|
|
test("`:tabopen qwant https://example.org<CR>` opens qwant.", async () => {
|
|
|
|
const driver = await getDriver()
|
|
|
|
return newTabWithoutChangingOldTabs(driver, async () => {
|
|
|
|
await sendKeys(driver, ":tabopen qwant https://example.org<CR>")
|
|
|
|
}).then(async ([newtab, _]) => {
|
|
|
|
// The new tab is active
|
|
|
|
expect(newtab.active).toEqual(true)
|
|
|
|
// Its url is qwant
|
|
|
|
await driver.wait(untilTabUrlMatches(driver, newtab.id, new RegExp("^https://www.qwant.com/.*example.org")), 10000)
|
|
|
|
}).finally(() => killDriver(driver))
|
|
|
|
})
|
|
|
|
|
|
|
|
test("`:tabopen search https://example.org<CR>` opens google.", async () => {
|
|
|
|
const driver = await getDriver()
|
|
|
|
return newTabWithoutChangingOldTabs(driver, async () => {
|
|
|
|
await sendKeys(driver, ":tabopen search https://example.org<CR>")
|
|
|
|
}).then(async ([newtab, _]) => {
|
|
|
|
// The new tab is active
|
|
|
|
expect(newtab.active).toEqual(true)
|
|
|
|
// The url is google.com
|
|
|
|
await driver.wait(untilTabUrlMatches(driver, newtab.id, new RegExp("^https://www.google.com/search.*example.org")), 10000)
|
|
|
|
}).finally(() => killDriver(driver))
|
2019-04-25 20:01:23 +02:00
|
|
|
})
|
|
|
|
})
|