-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #6 from flowplayer/feat/PLAY-473/tests
feat(PLAY-473): setup puppeteer
- Loading branch information
Showing
18 changed files
with
2,087 additions
and
46 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
import { FlowplayerUMD} from "@flowplayer/player" | ||
import type { FlowplayerUMD} from "@flowplayer/player" | ||
declare global { | ||
var flowplayer: FlowplayerUMD; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
export default { | ||
files: ["./test/puppeteer/**/*.spec.mjs"], | ||
require: ["./test/puppeteer/_setup/helpers.mjs"], | ||
timeout: "5m", | ||
concurrency: 3 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,202 @@ | ||
import pug from "pug" | ||
import os, {tmpdir} from "os" | ||
import fs from "fs/promises" | ||
import path from "path" | ||
import puppeteer from "puppeteer" | ||
import HttpServer from "http-server" | ||
import "dotenv/config" | ||
import mkdirp from "mkdirp" | ||
import {PuppeteerScreenRecorder} from "puppeteer-screen-recorder" | ||
|
||
const debug = (...args) => process.env.DEBUG && console.log(...args) | ||
const _dirname = path.dirname(import.meta.url) | ||
const tmpDir = path.join(os.tmpdir(), "puppeteer-native", "html") | ||
|
||
export function compilePug(name) { | ||
const fileName = path.join(_dirname, name + ".pug").slice(5) | ||
return pug.compileFile(fileName, {}) | ||
} | ||
|
||
const Templates = {local: compilePug("local")} | ||
|
||
export async function createServer(t) { | ||
return new Promise((resolve) => { | ||
const server = HttpServer.createServer({root: tmpDir}) | ||
server.listen(0, "localhost", () => { | ||
Object.assign(t.context, {server, ...server.server.address()}) | ||
Object.assign(t.context, {address: "localhost"}) | ||
debug("server / up / %s:%s", t.context.address, t.context.port) | ||
resolve() | ||
}) | ||
}) | ||
} | ||
|
||
export async function destroyServer(t) { | ||
if (!t.context.server) return | ||
t.context.server.close() | ||
t.context.server = void 0 | ||
} | ||
|
||
export async function loadComponent(componentName) { | ||
const component = await fs.readFile( | ||
path.join("dist", `${componentName}.js`) | ||
) | ||
|
||
return component.toString() | ||
} | ||
|
||
export async function writeTempFile(fileName, contents) { | ||
const fullName = path.join(tmpDir, fileName) | ||
debug("created: %s", fullName) | ||
await fs.writeFile(fullName, contents) | ||
return fullName | ||
} | ||
|
||
export function titleToFile(title, suffix = ".html") { | ||
const fileName = title | ||
.toLowerCase() | ||
.replaceAll(/\s+/g, "-") | ||
.replaceAll(/[/]/g, "-") | ||
.replaceAll(/,/g, "-") | ||
.replaceAll(/-{2,}/g, "-") | ||
.replaceAll(/=/g, "-") | ||
.replaceAll(/_/g, "-") | ||
return fileName + suffix | ||
} | ||
|
||
export async function compileLocalTest(t, {config, componentNames}) { | ||
const components = await Promise.all(componentNames.map((name) => loadComponent(name))) | ||
|
||
const compiled = Templates.local({ | ||
Test: { | ||
components, | ||
config: config | ||
} | ||
}) | ||
const fileName = titleToFile(t.title) | ||
const filePath = await writeTempFile(fileName, compiled) | ||
return {fileName, filePath} | ||
} | ||
|
||
const { | ||
PUPPETEER_PRODUCT: puppeteerProduct, | ||
//Applications/Google Chrome.app/Contents/MacOS/Google Chrome | ||
CHROME_PATH: chromePath = "/usr/bin/google-chrome", | ||
FIREFOX_PATH: firefoxPath = "/usr/bin/firefox" | ||
} = process.env | ||
|
||
const headless = !!process.env.CI || !!process.env.PUPPETEER_HEADLESS | ||
|
||
const executablePath = puppeteerProduct === "firefox" ? firefoxPath : chromePath | ||
|
||
export async function makePuppeteerSession(t, args) { | ||
args = args || [] | ||
if (process.env.CI) args.push("--no-sandbox") | ||
|
||
const browser = await puppeteer.launch({ | ||
headless, | ||
executablePath, | ||
slowMo: !headless ? 50 : 200, | ||
userDataDir: path.join(tmpdir(), `puppeteer-ci-${Date.now()}`), | ||
args | ||
}) | ||
const page = await browser.newPage({timeout: 120 * 1000}) | ||
const recorder = new PuppeteerScreenRecorder(page) | ||
page.on("console", (msg) => { | ||
if (!process.env.DEBUG) return | ||
for (let i = 0; i < msg.args().length; ++i) | ||
debug(`console / ${t.title} / ${i}: ${msg.args()[i]}`) | ||
}) | ||
return {browser, page, recorder} | ||
} | ||
|
||
async function getPlayer( | ||
t, | ||
componentNames, | ||
config, | ||
token, | ||
puppeteer, | ||
recorder, | ||
setup, | ||
host, | ||
page | ||
) { | ||
const context = {host, page} | ||
if (typeof setup == "function") { | ||
await setup(context) | ||
} | ||
const recordingFile = | ||
"/tmp/puppeteer-native/components/recordings/" + titleToFile(t.title, ".mp4") | ||
|
||
if (!process.env.DEBUG) await recorder.start(recordingFile) | ||
const {fileName, filePath} = await compileLocalTest(t, { | ||
config: JSON.stringify(config).replaceAll(":host:", host), | ||
componentNames, | ||
}) | ||
const testDocument = `${context.host}/${fileName}` //host + fileName | ||
await page.goto(testDocument, puppeteer || {waitUntil: "networkidle2", timeout: 120 * 1000} | ||
) | ||
const player = await page.$(".fp-engine") | ||
const components = await Promise.all(componentNames.map((name) => page.$(name))) | ||
return {components, player, filePath, recordingFile} | ||
} | ||
|
||
export function withComponents({componentNames, config, token, files, puppeteer, args}, setup) { | ||
return async function (t, run) { | ||
await mkdirp(tmpDir) | ||
await mkdirp("/tmp/puppeteer-native/components/recordings/") | ||
if (!t.context.address) await createServer(t) | ||
const host = `http://localhost:${t.context.port}` | ||
if (files) | ||
await Promise.all( | ||
Object.entries(files).map(([fileName, contents]) => | ||
writeTempFile(fileName, contents) | ||
) | ||
) | ||
|
||
const {browser, page, recorder} = await makePuppeteerSession(t, args) | ||
|
||
const {components, player, filePath, recordingFile} = await getPlayer( | ||
t, | ||
componentNames, | ||
config, | ||
token, | ||
puppeteer, | ||
recorder, | ||
setup, | ||
host, | ||
page | ||
) | ||
|
||
//ensure we have player | ||
t.assert(player) | ||
//ensure the components are rendered | ||
components.forEach((component, idx) => t.assert(component, componentNames[idx])) | ||
try { | ||
try { | ||
await run(t, page, player, ...components) | ||
} catch (err) { | ||
// todo: open an issue with ava.js about this | ||
t.fail(err.message) | ||
} | ||
} finally { | ||
await destroyServer(t) | ||
if (!process.env.DEBUG) await recorder.stop() | ||
if (t.passed) { | ||
const testFiles = Object.keys(files || {}).map((fileName) => | ||
path.join(tmpDir, fileName) | ||
) | ||
const rm = async (f) => { | ||
if (process.env.KEEP) return | ||
if (process.env.DEBUG) debug("cleaning up file %s", f) | ||
if (await fs.stat(f)) { | ||
return await fs.rm(f) | ||
} | ||
} | ||
const removals = [rm(recordingFile), rm(filePath), ...testFiles.map(rm)] | ||
await Promise.all(removals) | ||
} | ||
if (!process.env.KEEP) await browser.close() | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
html(lang="en") | ||
meta(charset="utf-8") | ||
head | ||
link(rel="stylesheet" href="https://cdn.flowplayer.com/releases/native/3/stable/style/flowplayer.css") | ||
body | ||
div(id = "player") | ||
script(type= "module") | ||
| import flowplayer from "https://cdn.flowplayer.com/releases/native/3/stable/esm/default/flowplayer.min.js" | ||
| import ovp from "https://cdn.flowplayer.com/releases/native/3/stable/esm/plugins/ovp.min.js" | ||
| import hls from "https://cdn.flowplayer.com/releases/native/3/stable/esm/plugins/hls.min.js" | ||
| import qsel from "https://cdn.flowplayer.com/releases/native/3/stable/esm/plugins/qsel.min.js" | ||
| import speed from "https://cdn.flowplayer.com/releases/native/3/stable/esm/plugins/speed.min.js" | ||
| window.flowplayer = flowplayer | ||
| flowplayer(ovp, hls, qsel, speed); | ||
each component in Test.components | ||
|!{component} | ||
| localStorage["flowplayer/debug"] = ".*" | ||
| window.Errors = [] | ||
| window.onerror = function(messageOrEvent, source, lineno, colno, error) { window.Errors.push([...arguments]) } | ||
| window.__FLOWPLAYER_TOKEN = "eyJraWQiOiJiRDJrSnhuTkppT1AiLCJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJjIjoie1wiYWNsXCI6NixcImlkXCI6XCJiRDJrSnhuTkppT1BcIn0iLCJpc3MiOiJGbG93cGxheWVyIn0.6SS-jLJb338KVYAsj4SFVBzbah-auDeQjeBqjJL6SRa_vJKt4xW7-lSlZGjqsKAqhXFso2UF_BaBkJZ1S1SQFg" | ||
| window.player = flowplayer("#player", !{Test.config}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
export async function captureEvent(page, player, eventName, timeout = 20_000) { | ||
return await page.evaluate( | ||
(player, eventName, timeout) => { | ||
return new Promise((ok, err) => { | ||
const serialize = (o) => { | ||
try { | ||
return JSON.parse(JSON.stringify(o)) | ||
} catch (err) { | ||
return o | ||
} | ||
} | ||
|
||
const handle = (e) => { | ||
ok({ | ||
type: e.type, | ||
state: player.root.className, | ||
detail: serialize(e.detail) | ||
}) | ||
} | ||
player.addEventListener(eventName, handle, {once: true}) | ||
setTimeout( | ||
() => | ||
err( | ||
new Error(`failed to detect event::${eventName} in ${timeout}ms`) | ||
), | ||
timeout | ||
) | ||
}) | ||
}, | ||
player, | ||
eventName, | ||
timeout | ||
) | ||
} | ||
|
||
export async function togglePlay(page, player, flag) { | ||
return await page.evaluate( | ||
(player, flag) => { | ||
return Promise.resolve(player.togglePlay(flag)) | ||
}, | ||
player, | ||
flag | ||
) | ||
} |
Oops, something went wrong.