mirror of
https://github.com/checkly/headless-recorder.git
synced 2021-07-28 02:03:42 +03:00
feat: initial commit
This commit is contained in:
7
.babelrc
Normal file
7
.babelrc
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"presets": [ "es2015", "stage-0"],
|
||||
"plugins": [
|
||||
"transform-runtime",
|
||||
"transform-object-rest-spread"
|
||||
]
|
||||
}
|
||||
32
.eslintrc.js
Normal file
32
.eslintrc.js
Normal file
@@ -0,0 +1,32 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
parser: 'babel-eslint',
|
||||
parserOptions: {
|
||||
sourceType: 'module'
|
||||
},
|
||||
env: {
|
||||
browser: true,
|
||||
node: true,
|
||||
"jest/globals": true
|
||||
|
||||
},
|
||||
// https://github.com/standard/standard/blob/master/docs/RULES-en.md
|
||||
extends: 'standard',
|
||||
// required to lint *.vue files
|
||||
plugins: [
|
||||
'html',
|
||||
'jest'
|
||||
],
|
||||
// add your custom rules here
|
||||
'rules': {
|
||||
// allow paren-less arrow functions
|
||||
'arrow-parens': 0,
|
||||
// allow async-await
|
||||
'generator-star-spacing': 0,
|
||||
// allow debugger during development
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0
|
||||
},
|
||||
globals: {
|
||||
chrome: false
|
||||
}
|
||||
}
|
||||
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
node_modules
|
||||
.DS_Store
|
||||
.idea
|
||||
build
|
||||
*.pem
|
||||
*.crx
|
||||
20
README.md
Normal file
20
README.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# Puppeteer Recorder
|
||||
|
||||
Puppeteer recorder is a Chrome extension that records your browser interactions and generates a
|
||||
[Puppeteer](https://github.com/GoogleChrome/puppeteer) script.
|
||||
|
||||
|
||||
## Known issues
|
||||
|
||||
- When navigating between pages, the script is only injected when the full navigation is done, 'committed' in Chrome extension
|
||||
speak. This means you might be able to see the page and click on stuff, but no events are recorded.
|
||||
|
||||
- Restarting a recording reloads the extension in the background. This is annoying and has to do with state, handlers
|
||||
and open message connections between parts of the extension misfiring.
|
||||
|
||||
|
||||
## Credits & disclaimer
|
||||
|
||||
Puppeteer recorder is the spiritual successor & love child of segment.io's
|
||||
[Daydream](https://github.com/segmentio/daydream) and [ui recorder](https://github.com/yguan/ui-recorder).
|
||||
|
||||
10855
package-lock.json
generated
Normal file
10855
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
71
package.json
Normal file
71
package.json
Normal file
@@ -0,0 +1,71 @@
|
||||
{
|
||||
"name": "puppeteer-recorder",
|
||||
"version": "0.1.0",
|
||||
"description": "A Chrome extension for recording browser interaction and generating Puppeteer scripts",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"dev": "NODE_ENV=development DEBUG=puppeteer-recorder:* webpack --watch",
|
||||
"build": "NODE_ENV=production webpack",
|
||||
"test": "jest"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/checkly/puppeteer-recorder"
|
||||
},
|
||||
"keywords": [
|
||||
"puppeteer",
|
||||
"chrome",
|
||||
"extension"
|
||||
],
|
||||
"author": "Tim Nolet",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/checkly/puppeteer-recorder/issues"
|
||||
},
|
||||
"homepage": "https://github.com/checkly/puppeteer-recorder#readme",
|
||||
"dependencies": {
|
||||
"css-selector-generator": "^1.0.2",
|
||||
"debug": "^3.1.0",
|
||||
"vue": "^2.5.17",
|
||||
"vue-clipboard2": "^0.2.1",
|
||||
"vue-highlightjs": "^1.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel": "^6.23.0",
|
||||
"babel-core": "^6.26.3",
|
||||
"babel-eslint": "^8.2.6",
|
||||
"babel-loader": "^7.1.5",
|
||||
"babel-plugin-transform-object-rest-spread": "^6.26.0",
|
||||
"babel-plugin-transform-runtime": "^6.23.0",
|
||||
"babel-preset-env": "^1.7.0",
|
||||
"babel-preset-es2015": "^6.24.1",
|
||||
"babel-preset-stage-0": "^6.24.1",
|
||||
"copy-webpack-plugin": "^4.5.2",
|
||||
"css-loader": "^1.0.0",
|
||||
"eslint": "^5.3.0",
|
||||
"eslint-config-standard": "^11.0.0",
|
||||
"eslint-plugin-html": "^4.0.5",
|
||||
"eslint-plugin-import": "^2.13.0",
|
||||
"eslint-plugin-jest": "^21.21.0",
|
||||
"eslint-plugin-node": "^7.0.1",
|
||||
"eslint-plugin-promise": "^3.8.0",
|
||||
"eslint-plugin-standard": "^3.1.0",
|
||||
"extract-text-webpack-plugin": "^3.0.2",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"jest": "^23.5.0",
|
||||
"node-sass": "^4.9.3",
|
||||
"sass-loader": "^7.1.0",
|
||||
"style-loader": "^0.22.1",
|
||||
"vue-loader": "^15.3.0",
|
||||
"vue-style-loader": "^4.1.1",
|
||||
"vue-template-compiler": "^2.5.17",
|
||||
"webpack": "^4.16.5",
|
||||
"webpack-chrome-extension-reloader": "^0.8.3",
|
||||
"webpack-cli": "^3.1.0"
|
||||
},
|
||||
"standard": {
|
||||
"globals": [
|
||||
"chrome"
|
||||
]
|
||||
}
|
||||
}
|
||||
84
src/background/index.js
Normal file
84
src/background/index.js
Normal file
@@ -0,0 +1,84 @@
|
||||
let recording = []
|
||||
let boundedMessageHandler
|
||||
let boundedNavigationHandler
|
||||
let scriptInjected = false
|
||||
|
||||
function boot () {
|
||||
chrome.extension.onConnect.addListener(port => {
|
||||
port.onMessage.addListener(msg => {
|
||||
if (msg.action && msg.action === 'start') start()
|
||||
if (msg.action && msg.action === 'stop') stop()
|
||||
if (msg.action && msg.action === 'restart') restart()
|
||||
})
|
||||
})
|
||||
chrome.browserAction.onClicked.addListener(() => {
|
||||
chrome.browserAction.setPopup({ popup: 'index.html' })
|
||||
})
|
||||
}
|
||||
|
||||
function start () {
|
||||
console.debug('start recording')
|
||||
|
||||
if (!scriptInjected) {
|
||||
chrome.tabs.executeScript({file: 'content-script.js'})
|
||||
scriptInjected = true
|
||||
}
|
||||
chrome.tabs.query({active: true, currentWindow: true}, tabs => {
|
||||
chrome.tabs.sendMessage(tabs[0].id, { action: 'get-current-url' }, response => {
|
||||
if (response) sendCurrentUrl(response.href)
|
||||
})
|
||||
})
|
||||
|
||||
boundedMessageHandler = handleEvent.bind(this)
|
||||
boundedNavigationHandler = handleNavigation.bind(this)
|
||||
|
||||
chrome.runtime.onMessage.addListener(boundedMessageHandler)
|
||||
chrome.webNavigation.onCompleted.addListener(boundedNavigationHandler)
|
||||
chrome.browserAction.setIcon({ path: './images/icon-green.png' })
|
||||
chrome.browserAction.setBadgeText({ text: 'rec' })
|
||||
chrome.browserAction.setBadgeBackgroundColor({ color: '#FF0000' })
|
||||
}
|
||||
|
||||
function stop () {
|
||||
console.debug('stop recording')
|
||||
chrome.runtime.onMessage.removeListener(boundedMessageHandler)
|
||||
chrome.webNavigation.onCompleted.removeListener(boundedNavigationHandler)
|
||||
chrome.browserAction.setIcon({ path: './images/icon-black.png' })
|
||||
chrome.browserAction.setBadgeText({ text: '1' })
|
||||
chrome.browserAction.setBadgeBackgroundColor({ color: '#45C8F1' })
|
||||
chrome.storage.local.set({ recording: recording }, () => {
|
||||
console.debug('recording stored')
|
||||
})
|
||||
}
|
||||
|
||||
function restart () {
|
||||
console.debug('restart')
|
||||
recording = []
|
||||
chrome.browserAction.setBadgeText({ text: '' })
|
||||
chrome.storage.local.remove('recording', () => {
|
||||
console.debug('stored recording cleared')
|
||||
})
|
||||
chrome.runtime.reload()
|
||||
}
|
||||
|
||||
function sendCurrentUrl (href) {
|
||||
handleEvent({ selector: undefined, value: undefined, action: 'click', href })
|
||||
}
|
||||
|
||||
function handleEvent (event) {
|
||||
console.debug('receiving event', event)
|
||||
recording.push(event)
|
||||
chrome.storage.local.set({ recording: recording }, () => {
|
||||
console.debug('stored recording updated')
|
||||
})
|
||||
}
|
||||
|
||||
function handleNavigation ({ url, frameId }) {
|
||||
console.debug(`current frame ${frameId} with url ${url}`)
|
||||
if (frameId === 0) {
|
||||
chrome.tabs.executeScript({file: 'content-script.js'})
|
||||
}
|
||||
}
|
||||
|
||||
console.debug('booting puppeteer-recorder')
|
||||
boot()
|
||||
6
src/content-scripts/elements-to-bind-to.js
Normal file
6
src/content-scripts/elements-to-bind-to.js
Normal file
@@ -0,0 +1,6 @@
|
||||
export default [
|
||||
'input',
|
||||
'textarea',
|
||||
'a',
|
||||
'button'
|
||||
]
|
||||
103
src/content-scripts/events-to-record.js
Normal file
103
src/content-scripts/events-to-record.js
Normal file
@@ -0,0 +1,103 @@
|
||||
export default [
|
||||
'click',
|
||||
// 'focus',
|
||||
// 'blur',
|
||||
'dblclick',
|
||||
'change',
|
||||
// 'keyup',
|
||||
'keydown',
|
||||
// 'keypress',
|
||||
// 'mousedown',
|
||||
// 'mousemove',
|
||||
// 'mouseout',
|
||||
// 'mouseover',
|
||||
// 'mouseup'
|
||||
// 'resize',
|
||||
// 'scroll',
|
||||
'select',
|
||||
'submit',
|
||||
'load',
|
||||
'unload'
|
||||
]
|
||||
|
||||
// const events = [
|
||||
// abort,
|
||||
// afterprint,
|
||||
// beforeprint,
|
||||
// beforeunload,
|
||||
// blur,
|
||||
// canplay,
|
||||
// canplaythrough,
|
||||
// change,
|
||||
// click,
|
||||
// contextmenu,
|
||||
// copy,
|
||||
// cuechange,
|
||||
// cut,
|
||||
// dblclick,
|
||||
// DOMContentLoaded,
|
||||
// drag,
|
||||
// dragend,
|
||||
// dragenter,
|
||||
// dragleave,
|
||||
// dragover,
|
||||
// dragstart,
|
||||
// drop,
|
||||
// durationchange,
|
||||
// emptied,
|
||||
// ended,
|
||||
// error,
|
||||
// focus,
|
||||
// focusin,
|
||||
// focusout,
|
||||
// formchange,
|
||||
// forminput,
|
||||
// hashchange,
|
||||
// input,
|
||||
// invalid,
|
||||
// keydown,
|
||||
// keypress,
|
||||
// keyup,
|
||||
// load,
|
||||
// loadeddata,
|
||||
// loadedmetadata,
|
||||
// loadstart,
|
||||
// message,
|
||||
// mousedown,
|
||||
// mouseenter,
|
||||
// mouseleave,
|
||||
// mousemove,
|
||||
// mouseout,
|
||||
// mouseover,
|
||||
// mouseup,
|
||||
// mousewheel,
|
||||
// offline,
|
||||
// online,
|
||||
// pagehide,
|
||||
// pageshow,
|
||||
// paste,
|
||||
// pause,
|
||||
// play,
|
||||
// playing,
|
||||
// popstate,
|
||||
// progress,
|
||||
// ratechange,
|
||||
// readystatechange,
|
||||
// redo,
|
||||
// reset,
|
||||
// resize,
|
||||
// scroll,
|
||||
// seeked,
|
||||
// seeking,
|
||||
// select,
|
||||
// show,
|
||||
// stalled,
|
||||
// storage,
|
||||
// submit,
|
||||
// suspend,
|
||||
// timeupdate,
|
||||
// undo,
|
||||
// unload,
|
||||
// volumechange,
|
||||
// waiting
|
||||
// ];
|
||||
58
src/content-scripts/index.js
Normal file
58
src/content-scripts/index.js
Normal file
@@ -0,0 +1,58 @@
|
||||
import eventsToRecord from './events-to-record'
|
||||
import elementsToBindTo from './elements-to-bind-to'
|
||||
import Selector from 'css-selector-generator'
|
||||
|
||||
const selector = new Selector()
|
||||
|
||||
class EventRecorder {
|
||||
start () {
|
||||
if (!window.eventRecorder) {
|
||||
console.debug('starting in EventRecorder')
|
||||
const elements = document.querySelectorAll(elementsToBindTo.join(','))
|
||||
|
||||
for (let i = 0; i < elements.length; i++) {
|
||||
for (let j = 0; j < eventsToRecord.length; j++) {
|
||||
elements[i].addEventListener(eventsToRecord[j], recordEvent)
|
||||
}
|
||||
}
|
||||
|
||||
chrome.runtime.onMessage.addListener((msg, sender, resp) => {
|
||||
console.debug('got message from background')
|
||||
if (msg.action && msg.action === 'get-current-url') {
|
||||
resp({ href: window.location.href })
|
||||
}
|
||||
})
|
||||
window.hasEventRecorder = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function recordEvent (e) {
|
||||
const msg = {
|
||||
selector: selector.getSelector(e.target),
|
||||
value: e.target.value,
|
||||
action: e.type,
|
||||
keyCode: e.keyCode ? e.keyCode : null,
|
||||
href: e.target.href ? e.target.href : null,
|
||||
coordinates: getCoordinates(e)
|
||||
}
|
||||
sendMessage(msg)
|
||||
}
|
||||
|
||||
function getCoordinates (evt) {
|
||||
const eventsWithCoordinates = {
|
||||
mouseup: true,
|
||||
mousedown: true,
|
||||
mousemove: true,
|
||||
mouseover: true
|
||||
}
|
||||
return eventsWithCoordinates[evt.type] ? { x: evt.clientX, y: evt.clientY } : null
|
||||
}
|
||||
|
||||
function sendMessage (msg) {
|
||||
console.debug('sending message', msg)
|
||||
chrome.runtime.sendMessage(msg)
|
||||
}
|
||||
|
||||
const eventRecorder = new EventRecorder()
|
||||
eventRecorder.start()
|
||||
252
src/images/Desert.svg
Normal file
252
src/images/Desert.svg
Normal file
@@ -0,0 +1,252 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 17.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="110px"
|
||||
height="110px" viewBox="0 0 110 110" style="enable-background:new 0 0 110 110;" xml:space="preserve">
|
||||
<g id="Artboard" style="display:none;">
|
||||
<rect x="-963" y="-178" style="display:inline;fill:#808080;" width="1376" height="359"/>
|
||||
</g>
|
||||
<g id="R-Multicolor" style="display:none;">
|
||||
<circle style="display:inline;fill:#32BEA6;" cx="55" cy="55" r="55"/>
|
||||
<g style="display:inline;">
|
||||
<path style="fill:#EDBC7C;" d="M83.879,79.879C83.316,79.316,82.553,79,81.757,79H28.243c-0.795,0-1.559,0.316-2.122,0.879L23,83
|
||||
v4h64v-4L83.879,79.879z"/>
|
||||
<path style="fill:#F8E1C2;" d="M23,83l3.121-3.121C26.684,79.316,27.447,79,28.243,79h53.515c0.796,0,1.559,0.316,2.121,0.879
|
||||
L87,83H23z"/>
|
||||
<circle style="fill:#FACB1B;" cx="31" cy="31" r="8"/>
|
||||
<path style="fill:#107665;" d="M76,35c-3.59,0-6,2.81-6,5.94v4.08c0,1.09-0.89,1.98-1.98,1.98h-0.04C66.89,47,66,46.11,66,45.02
|
||||
V31c0-4.42-3.58-8-8-8s-8,3.58-8,8v22.02c0,1.09-0.89,1.98-1.98,1.98h-0.04C46.89,55,46,54.11,46,53.02v-4.08
|
||||
c0-3.248-2.54-5.94-6-5.94c-3.427,0-6,2.658-6,5.94v10.21C34,71.17,50,66.83,50,79v4h16V71c0-12.17,16-7.83,16-19.85V40.94
|
||||
C82,37.66,79.28,35,76,35z"/>
|
||||
<path style="fill:#0D9681;" d="M50,31v22.02c0,1.09-0.89,1.98-1.98,1.98h-0.04C46.89,55,46,54.11,46,53.02v-4.08
|
||||
c0-3.248-2.54-5.94-6-5.94c-3.427,0-6,2.658-6,5.94v10.21C34,71.17,50,66.83,50,79v4h8V23C53.58,23,50,26.58,50,31z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="Multicolor">
|
||||
<g>
|
||||
<path style="fill:#EDBC7C;" d="M83.879,79.879C83.316,79.316,82.553,79,81.757,79H28.243c-0.795,0-1.559,0.316-2.122,0.879L23,83
|
||||
v4h64v-4L83.879,79.879z"/>
|
||||
<path style="fill:#F8E1C2;" d="M23,83l3.121-3.121C26.684,79.316,27.447,79,28.243,79h53.515c0.796,0,1.559,0.316,2.121,0.879
|
||||
L87,83H23z"/>
|
||||
<circle style="fill:#FACB1B;" cx="31" cy="31" r="8"/>
|
||||
<path style="fill:#107665;" d="M76,35c-3.59,0-6,2.81-6,5.94v4.08c0,1.09-0.89,1.98-1.98,1.98h-0.04C66.89,47,66,46.11,66,45.02
|
||||
V31c0-4.42-3.58-8-8-8s-8,3.58-8,8v22.02c0,1.09-0.89,1.98-1.98,1.98h-0.04C46.89,55,46,54.11,46,53.02v-4.08
|
||||
c0-3.248-2.54-5.94-6-5.94c-3.427,0-6,2.658-6,5.94v10.21C34,71.17,50,66.83,50,79v4h16V71c0-12.17,16-7.83,16-19.85V40.94
|
||||
C82,37.66,79.28,35,76,35z"/>
|
||||
<path style="fill:#0D9681;" d="M50,31v22.02c0,1.09-0.89,1.98-1.98,1.98h-0.04C46.89,55,46,54.11,46,53.02v-4.08
|
||||
c0-3.248-2.54-5.94-6-5.94c-3.427,0-6,2.658-6,5.94v10.21C34,71.17,50,66.83,50,79v4h8V23C53.58,23,50,26.58,50,31z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="Blue" style="display:none;">
|
||||
<g style="display:inline;">
|
||||
<g>
|
||||
<path style="fill:#53BAD4;" d="M83.879,79.879C83.316,79.316,82.553,79,81.757,79H28.243c-0.795,0-1.559,0.316-2.122,0.879L23,83
|
||||
v4h64v-4L83.879,79.879z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path style="fill:#BBE7F2;" d="M23,83l3.121-3.121C26.684,79.316,27.447,79,28.243,79h53.515c0.795,0,1.559,0.316,2.122,0.879
|
||||
L87,83H23z"/>
|
||||
</g>
|
||||
<g>
|
||||
<circle style="fill:#FFFFFF;" cx="31" cy="31" r="8"/>
|
||||
</g>
|
||||
<g>
|
||||
<path style="fill:#0081A1;" d="M76,35c-3.59,0-6,2.81-6,5.94v4.08c0,1.09-0.89,1.98-1.98,1.98h-0.04C66.89,47,66,46.11,66,45.02
|
||||
V31c0-4.42-3.58-8-8-8s-8,3.58-8,8v22.02c0,1.09-0.89,1.98-1.98,1.98h-0.04C46.89,55,46,54.11,46,53.02v-4.08
|
||||
c0-3.247-2.54-5.94-6-5.94c-3.427,0-6,2.658-6,5.94v10.21C34,71.17,50,66.83,50,79v4h16V71c0-12.17,16-7.83,16-19.85V40.94
|
||||
C82,37.66,79.28,35,76,35z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path style="fill:#009FC7;" d="M50,31v22.02c0,1.09-0.89,1.98-1.98,1.98h-0.04C46.89,55,46,54.11,46,53.02v-4.08
|
||||
c0-3.247-2.54-5.94-6-5.94c-3.427,0-6,2.658-6,5.94v10.21C34,71.17,50,66.83,50,79v4h8V23C53.58,23,50,26.58,50,31z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="R-Blue" style="display:none;">
|
||||
<g style="display:inline;">
|
||||
<circle style="fill:#81D2EB;" cx="55" cy="55" r="55"/>
|
||||
</g>
|
||||
<g style="display:inline;">
|
||||
<g>
|
||||
<path style="fill:#53BAD4;" d="M83.879,79.879C83.316,79.316,82.553,79,81.757,79H28.243c-0.795,0-1.559,0.316-2.122,0.879L23,83
|
||||
v4h64v-4L83.879,79.879z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path style="fill:#BBE7F2;" d="M23,83l3.121-3.121C26.684,79.316,27.447,79,28.243,79h53.515c0.795,0,1.559,0.316,2.122,0.879
|
||||
L87,83H23z"/>
|
||||
</g>
|
||||
<g>
|
||||
<circle style="fill:#FFFFFF;" cx="31" cy="31" r="8"/>
|
||||
</g>
|
||||
<g>
|
||||
<path style="fill:#0081A1;" d="M76,35c-3.59,0-6,2.81-6,5.94v4.08c0,1.09-0.89,1.98-1.98,1.98h-0.04C66.89,47,66,46.11,66,45.02
|
||||
V31c0-4.42-3.58-8-8-8s-8,3.58-8,8v22.02c0,1.09-0.89,1.98-1.98,1.98h-0.04C46.89,55,46,54.11,46,53.02v-4.08
|
||||
c0-3.247-2.54-5.94-6-5.94c-3.427,0-6,2.658-6,5.94v10.21C34,71.17,50,66.83,50,79v4h16V71c0-12.17,16-7.83,16-19.85V40.94
|
||||
C82,37.66,79.28,35,76,35z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path style="fill:#009FC7;" d="M50,31v22.02c0,1.09-0.89,1.98-1.98,1.98h-0.04C46.89,55,46,54.11,46,53.02v-4.08
|
||||
c0-3.247-2.54-5.94-6-5.94c-3.427,0-6,2.658-6,5.94v10.21C34,71.17,50,66.83,50,79v4h8V23C53.58,23,50,26.58,50,31z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="Green" style="display:none;">
|
||||
<g style="display:inline;">
|
||||
<g>
|
||||
<path style="fill:#5DCFC3;" d="M83.879,79.879C83.316,79.316,82.553,79,81.757,79H28.243c-0.795,0-1.559,0.316-2.122,0.879L23,83
|
||||
v4h64v-4L83.879,79.879z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path style="fill:#AAF0E9;" d="M23,83l3.121-3.121C26.684,79.316,27.447,79,28.243,79h53.515c0.795,0,1.559,0.316,2.122,0.879
|
||||
L87,83H23z"/>
|
||||
</g>
|
||||
<g>
|
||||
<circle style="fill:#FFFFFF;" cx="31" cy="31" r="8"/>
|
||||
</g>
|
||||
<g>
|
||||
<path style="fill:#009687;" d="M76,35c-3.59,0-6,2.81-6,5.94v4.08c0,1.09-0.89,1.98-1.98,1.98h-0.04C66.89,47,66,46.11,66,45.02
|
||||
V31c0-4.42-3.58-8-8-8s-8,3.58-8,8v22.02c0,1.09-0.89,1.98-1.98,1.98h-0.04C46.89,55,46,54.11,46,53.02v-4.08
|
||||
c0-3.247-2.54-5.94-6-5.94c-3.427,0-6,2.658-6,5.94v10.21C34,71.17,50,66.83,50,79v4h16V71c0-12.17,16-7.83,16-19.85V40.94
|
||||
C82,37.66,79.28,35,76,35z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path style="fill:#00B8A5;" d="M50,31v22.02c0,1.09-0.89,1.98-1.98,1.98h-0.04C46.89,55,46,54.11,46,53.02v-4.08
|
||||
c0-3.247-2.54-5.94-6-5.94c-3.427,0-6,2.658-6,5.94v10.21C34,71.17,50,66.83,50,79v4h8V23C53.58,23,50,26.58,50,31z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="R-Green" style="display:none;">
|
||||
<g style="display:inline;">
|
||||
<circle style="fill:#87E0C8;" cx="55" cy="55" r="55"/>
|
||||
</g>
|
||||
<g style="display:inline;">
|
||||
<g>
|
||||
<path style="fill:#5DCFC3;" d="M83.879,79.879C83.316,79.316,82.553,79,81.757,79H28.243c-0.795,0-1.559,0.316-2.122,0.879L23,83
|
||||
v4h64v-4L83.879,79.879z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path style="fill:#AAF0E9;" d="M23,83l3.121-3.121C26.684,79.316,27.447,79,28.243,79h53.515c0.795,0,1.559,0.316,2.122,0.879
|
||||
L87,83H23z"/>
|
||||
</g>
|
||||
<g>
|
||||
<circle style="fill:#FFFFFF;" cx="31" cy="31" r="8"/>
|
||||
</g>
|
||||
<g>
|
||||
<path style="fill:#009687;" d="M76,35c-3.59,0-6,2.81-6,5.94v4.08c0,1.09-0.89,1.98-1.98,1.98h-0.04C66.89,47,66,46.11,66,45.02
|
||||
V31c0-4.42-3.58-8-8-8s-8,3.58-8,8v22.02c0,1.09-0.89,1.98-1.98,1.98h-0.04C46.89,55,46,54.11,46,53.02v-4.08
|
||||
c0-3.247-2.54-5.94-6-5.94c-3.427,0-6,2.658-6,5.94v10.21C34,71.17,50,66.83,50,79v4h16V71c0-12.17,16-7.83,16-19.85V40.94
|
||||
C82,37.66,79.28,35,76,35z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path style="fill:#00B8A5;" d="M50,31v22.02c0,1.09-0.89,1.98-1.98,1.98h-0.04C46.89,55,46,54.11,46,53.02v-4.08
|
||||
c0-3.247-2.54-5.94-6-5.94c-3.427,0-6,2.658-6,5.94v10.21C34,71.17,50,66.83,50,79v4h8V23C53.58,23,50,26.58,50,31z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="Red" style="display:none;">
|
||||
<g style="display:inline;">
|
||||
<g>
|
||||
<path style="fill:#E8A099;" d="M83.879,79.879C83.316,79.316,82.553,79,81.757,79H28.243c-0.795,0-1.559,0.316-2.122,0.879L23,83
|
||||
v4h64v-4L83.879,79.879z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path style="fill:#FFD7D4;" d="M23,83l3.121-3.121C26.684,79.316,27.447,79,28.243,79h53.515c0.795,0,1.559,0.316,2.122,0.879
|
||||
L87,83H23z"/>
|
||||
</g>
|
||||
<g>
|
||||
<circle style="fill:#FFFFFF;" cx="31" cy="31" r="8"/>
|
||||
</g>
|
||||
<g>
|
||||
<path style="fill:#C23023;" d="M76,35c-3.59,0-6,2.81-6,5.94v4.08c0,1.09-0.89,1.98-1.98,1.98h-0.04C66.89,47,66,46.11,66,45.02
|
||||
V31c0-4.42-3.58-8-8-8s-8,3.58-8,8v22.02c0,1.09-0.89,1.98-1.98,1.98h-0.04C46.89,55,46,54.11,46,53.02v-4.08
|
||||
c0-3.247-2.54-5.94-6-5.94c-3.427,0-6,2.658-6,5.94v10.21C34,71.17,50,66.83,50,79v4h16V71c0-12.17,16-7.83,16-19.85V40.94
|
||||
C82,37.66,79.28,35,76,35z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path style="fill:#E54B44;" d="M50,31v22.02c0,1.09-0.89,1.98-1.98,1.98h-0.04C46.89,55,46,54.11,46,53.02v-4.08
|
||||
c0-3.247-2.54-5.94-6-5.94c-3.427,0-6,2.658-6,5.94v10.21C34,71.17,50,66.83,50,79v4h8V23C53.58,23,50,26.58,50,31z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="R-Red" style="display:none;">
|
||||
<g style="display:inline;">
|
||||
<circle style="fill:#FABBAF;" cx="55" cy="55" r="55"/>
|
||||
</g>
|
||||
<g style="display:inline;">
|
||||
<g>
|
||||
<path style="fill:#E8A099;" d="M83.879,79.879C83.316,79.316,82.553,79,81.757,79H28.243c-0.795,0-1.559,0.316-2.122,0.879L23,83
|
||||
v4h64v-4L83.879,79.879z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path style="fill:#FFD7D4;" d="M23,83l3.121-3.121C26.684,79.316,27.447,79,28.243,79h53.515c0.795,0,1.559,0.316,2.122,0.879
|
||||
L87,83H23z"/>
|
||||
</g>
|
||||
<g>
|
||||
<circle style="fill:#FFFFFF;" cx="31" cy="31" r="8"/>
|
||||
</g>
|
||||
<g>
|
||||
<path style="fill:#C23023;" d="M76,35c-3.59,0-6,2.81-6,5.94v4.08c0,1.09-0.89,1.98-1.98,1.98h-0.04C66.89,47,66,46.11,66,45.02
|
||||
V31c0-4.42-3.58-8-8-8s-8,3.58-8,8v22.02c0,1.09-0.89,1.98-1.98,1.98h-0.04C46.89,55,46,54.11,46,53.02v-4.08
|
||||
c0-3.247-2.54-5.94-6-5.94c-3.427,0-6,2.658-6,5.94v10.21C34,71.17,50,66.83,50,79v4h16V71c0-12.17,16-7.83,16-19.85V40.94
|
||||
C82,37.66,79.28,35,76,35z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path style="fill:#E54B44;" d="M50,31v22.02c0,1.09-0.89,1.98-1.98,1.98h-0.04C46.89,55,46,54.11,46,53.02v-4.08
|
||||
c0-3.247-2.54-5.94-6-5.94c-3.427,0-6,2.658-6,5.94v10.21C34,71.17,50,66.83,50,79v4h8V23C53.58,23,50,26.58,50,31z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="Yellow" style="display:none;">
|
||||
<g style="display:inline;">
|
||||
<g>
|
||||
<path style="fill:#F5C43D;" d="M83.879,79.879C83.316,79.316,82.553,79,81.757,79H28.243c-0.795,0-1.559,0.316-2.122,0.879L23,83
|
||||
v4h64v-4L83.879,79.879z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path style="fill:#FFE9A1;" d="M23,83l3.121-3.121C26.684,79.316,27.447,79,28.243,79h53.515c0.795,0,1.559,0.316,2.122,0.879
|
||||
L87,83H23z"/>
|
||||
</g>
|
||||
<g>
|
||||
<circle style="fill:#FFFFFF;" cx="31" cy="31" r="8"/>
|
||||
</g>
|
||||
<g>
|
||||
<path style="fill:#E07000;" d="M76,35c-3.59,0-6,2.81-6,5.94v4.08c0,1.09-0.89,1.98-1.98,1.98h-0.04C66.89,47,66,46.11,66,45.02
|
||||
V31c0-4.42-3.58-8-8-8s-8,3.58-8,8v22.02c0,1.09-0.89,1.98-1.98,1.98h-0.04C46.89,55,46,54.11,46,53.02v-4.08
|
||||
c0-3.247-2.54-5.94-6-5.94c-3.427,0-6,2.658-6,5.94v10.21C34,71.17,50,66.83,50,79v4h16V71c0-12.17,16-7.83,16-19.85V40.94
|
||||
C82,37.66,79.28,35,76,35z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path style="fill:#FA9200;" d="M50,31v22.02c0,1.09-0.89,1.98-1.98,1.98h-0.04C46.89,55,46,54.11,46,53.02v-4.08
|
||||
c0-3.247-2.54-5.94-6-5.94c-3.427,0-6,2.658-6,5.94v10.21C34,71.17,50,66.83,50,79v4h8V23C53.58,23,50,26.58,50,31z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g id="R-Yellow" style="display:none;">
|
||||
<g style="display:inline;">
|
||||
<circle style="fill:#FFD75E;" cx="55" cy="55" r="55"/>
|
||||
</g>
|
||||
<g style="display:inline;">
|
||||
<g>
|
||||
<path style="fill:#F5C43D;" d="M83.879,79.879C83.316,79.316,82.553,79,81.757,79H28.243c-0.795,0-1.559,0.316-2.122,0.879L23,83
|
||||
v4h64v-4L83.879,79.879z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path style="fill:#FFE9A1;" d="M23,83l3.121-3.121C26.684,79.316,27.447,79,28.243,79h53.515c0.795,0,1.559,0.316,2.122,0.879
|
||||
L87,83H23z"/>
|
||||
</g>
|
||||
<g>
|
||||
<circle style="fill:#FFFFFF;" cx="31" cy="31" r="8"/>
|
||||
</g>
|
||||
<g>
|
||||
<path style="fill:#E07000;" d="M76,35c-3.59,0-6,2.81-6,5.94v4.08c0,1.09-0.89,1.98-1.98,1.98h-0.04C66.89,47,66,46.11,66,45.02
|
||||
V31c0-4.42-3.58-8-8-8s-8,3.58-8,8v22.02c0,1.09-0.89,1.98-1.98,1.98h-0.04C46.89,55,46,54.11,46,53.02v-4.08
|
||||
c0-3.247-2.54-5.94-6-5.94c-3.427,0-6,2.658-6,5.94v10.21C34,71.17,50,66.83,50,79v4h16V71c0-12.17,16-7.83,16-19.85V40.94
|
||||
C82,37.66,79.28,35,76,35z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path style="fill:#FA9200;" d="M50,31v22.02c0,1.09-0.89,1.98-1.98,1.98h-0.04C46.89,55,46,54.11,46,53.02v-4.08
|
||||
c0-3.247-2.54-5.94-6-5.94c-3.427,0-6,2.658-6,5.94v10.21C34,71.17,50,66.83,50,79v4h8V23C53.58,23,50,26.58,50,31z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 12 KiB |
BIN
src/images/icon-black.png
Normal file
BIN
src/images/icon-black.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.0 KiB |
BIN
src/images/icon-green.png
Normal file
BIN
src/images/icon-green.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.0 KiB |
12
src/images/settings.svg
Normal file
12
src/images/settings.svg
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="32px" height="32px" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 48.2 (47327) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>_ionicons_svg_ios-settings</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="_ionicons_svg_ios-settings" fill="#8492A6" fill-rule="nonzero">
|
||||
<path d="M29.3583333,16 C29.3583333,14.25 30.45,12.7583333 32,12.1583333 C31.5916667,10.45 30.9166667,8.85 30.025,7.4 C29.4916667,7.63333333 28.925,7.75833333 28.35,7.75833333 C27.3,7.75833333 26.25,7.35833333 25.4416667,6.55833333 C24.2,5.31666667 23.925,3.49166667 24.5916667,1.975 C23.15,1.08333333 21.5416667,0.408333333 19.8416667,1.18423789e-15 C19.25,1.54166667 17.75,2.64166667 16,2.64166667 C14.25,2.64166667 12.75,1.54166667 12.1583333,0 C10.45,0.408333333 8.85,1.08333333 7.4,1.975 C8.075,3.48333333 7.79166667,5.31666667 6.55,6.55833333 C5.75,7.35833333 4.69166667,7.75833333 3.64166667,7.75833333 C3.06666667,7.75833333 2.5,7.64166667 1.96666667,7.4 C1.08333333,8.85833333 0.408333333,10.4583333 0,12.1666667 C1.54166667,12.7583333 2.64166667,14.25 2.64166667,16.0083333 C2.64166667,17.7583333 1.55,19.25 0.00833333333,19.85 C0.416666667,21.5583333 1.09166667,23.1583333 1.98333333,24.6083333 C2.51666667,24.375 3.08333333,24.2583333 3.65,24.2583333 C4.7,24.2583333 5.75,24.6583333 6.55833333,25.4583333 C7.79166667,26.6916667 8.075,28.525 7.40833333,30.0333333 C8.85833333,30.925 10.4666667,31.6 12.1666667,32.0083333 C12.7583333,30.4666667 14.25,29.375 16,29.375 C17.75,29.375 19.2416667,30.4666667 19.8333333,32.0083333 C21.5416667,31.6 23.1416667,30.925 24.5916667,30.0333333 C23.925,28.525 24.2083333,26.7 25.4416667,25.4583333 C26.2416667,24.6583333 27.2916667,24.2583333 28.35,24.2583333 C28.9166667,24.2583333 29.4916667,24.375 30.0166667,24.6083333 C30.9083333,23.1583333 31.5833333,21.55 31.9916667,19.85 C30.4583333,19.25 29.3583333,17.7583333 29.3583333,16 Z M16.075,22.6583333 C12.3833333,22.6583333 9.40833333,19.6666667 9.40833333,15.9916667 C9.40833333,12.3166667 12.3833333,9.325 16.075,9.325 C19.7666667,9.325 22.7416667,12.3166667 22.7416667,15.9916667 C22.7416667,19.6666667 19.7666667,22.6583333 16.075,22.6583333 Z" id="Shape"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
27
src/manifest.json
Normal file
27
src/manifest.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "Puppeteer Recorder",
|
||||
"version": "1.0.0",
|
||||
"manifest_version": 2,
|
||||
"description": "A Chrome extension for recording browser interaction and generating Puppeteer scripts",
|
||||
"permissions": [
|
||||
"storage",
|
||||
"webNavigation",
|
||||
"tabs",
|
||||
"*://*/"
|
||||
],
|
||||
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
|
||||
"browser_action": {
|
||||
"default_icon": "images/icon-black.png",
|
||||
"default_title": "Puppeteer Recorder"
|
||||
},
|
||||
"background": {
|
||||
"scripts": [
|
||||
"background.js"
|
||||
],
|
||||
"persistent": false
|
||||
},
|
||||
"options_ui": {
|
||||
"page": "options.html",
|
||||
"open_in_tab": true
|
||||
}
|
||||
}
|
||||
22
src/options/options.html
Normal file
22
src/options/options.html
Normal file
@@ -0,0 +1,22 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Options for Puppeteer recorder</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1>Options</h1>
|
||||
<hr>
|
||||
<h3>Code generator options</h3>
|
||||
<label>
|
||||
<input type="checkbox" id="asyncWrapper">
|
||||
wrap code in async function
|
||||
</label>
|
||||
<hr>
|
||||
|
||||
<div id="status"></div>
|
||||
<button id="save">Save</button>
|
||||
|
||||
<script src="options.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
29
src/options/options.js
Normal file
29
src/options/options.js
Normal file
@@ -0,0 +1,29 @@
|
||||
// Saves options to chrome.storage
|
||||
const defaults = {
|
||||
codeOptions: {
|
||||
asyncWrapper: true
|
||||
}
|
||||
}
|
||||
|
||||
function saveOptions () {
|
||||
const asyncWrapper = document.getElementById('asyncWrapper').checked
|
||||
chrome.storage.local.set({
|
||||
codeOptions: {
|
||||
asyncWrapper
|
||||
}
|
||||
}, () => {
|
||||
var status = document.getElementById('status')
|
||||
status.textContent = 'Options saved.'
|
||||
setTimeout(() => { status.textContent = '' }, 750)
|
||||
})
|
||||
}
|
||||
|
||||
function restoreOptions () {
|
||||
// Use default value color = 'red' and likesColor = true.
|
||||
chrome.storage.local.get(defaults, (items) => {
|
||||
console.log(items)
|
||||
document.getElementById('asyncWrapper').checked = items.codeOptions.asyncWrapper
|
||||
})
|
||||
}
|
||||
document.addEventListener('DOMContentLoaded', restoreOptions)
|
||||
document.getElementById('save').addEventListener('click', saveOptions)
|
||||
131
src/popup/_animations.scss
Normal file
131
src/popup/_animations.scss
Normal file
@@ -0,0 +1,131 @@
|
||||
.shake {
|
||||
animation: shake 0.82s cubic-bezier(.36,.07,.19,.97) both;
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
|
||||
@keyframes shake {
|
||||
10%, 90% {
|
||||
transform: translate3d(-1px, 0, 0);
|
||||
}
|
||||
|
||||
20%, 80% {
|
||||
transform: translate3d(2px, 0, 0);
|
||||
}
|
||||
|
||||
30%, 50%, 70% {
|
||||
transform: translate3d(-4px, 0, 0);
|
||||
}
|
||||
|
||||
40%, 60% {
|
||||
transform: translate3d(4px, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
.pulse {
|
||||
animation: pulse 2s ease infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
opacity: .7;
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: .4;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: .7;
|
||||
}
|
||||
}
|
||||
|
||||
.flash-once {
|
||||
animation: flash-once 3.5s ease 1;
|
||||
}
|
||||
|
||||
@keyframes fade-up {
|
||||
0% {
|
||||
transform: translate3d(0, 10px, 0);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translate3d(0, 0, 0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.fade-in {
|
||||
animation: fade-in .3s ease-in-out;
|
||||
}
|
||||
|
||||
@keyframes fade-in {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.spin {
|
||||
animation-name: spin;
|
||||
animation-duration: 2000ms;
|
||||
animation-iteration-count: infinite;
|
||||
animation-timing-function: linear;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
from {transform:rotate(0deg);}
|
||||
to {transform:rotate(360deg);}
|
||||
}
|
||||
|
||||
|
||||
.bounceIn {
|
||||
animation-name: bounceIn;
|
||||
transform-origin: center bottom;
|
||||
animation-duration: 1s;
|
||||
animation-fill-mode: both;
|
||||
animation-iteration-count: 1;
|
||||
}
|
||||
|
||||
@keyframes bounceIn {
|
||||
0%, 20%, 40%, 60%, 80%, 100% {
|
||||
-webkit-transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
|
||||
transition-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
|
||||
}
|
||||
|
||||
0% {
|
||||
opacity: 1;
|
||||
-webkit-transform: scale3d(.8, .8, .8);
|
||||
transform: scale3d(.8, .8, .8);
|
||||
}
|
||||
|
||||
20% {
|
||||
-webkit-transform: scale3d(1.1, 1.1, 1.1);
|
||||
transform: scale3d(1.1, 1.1, 1.1);
|
||||
}
|
||||
|
||||
40% {
|
||||
-webkit-transform: scale3d(.9, .9, .9);
|
||||
transform: scale3d(.9, .9, .9);
|
||||
}
|
||||
|
||||
60% {
|
||||
opacity: 1;
|
||||
-webkit-transform: scale3d(1.03, 1.03, 1.03);
|
||||
transform: scale3d(1.03, 1.03, 1.03);
|
||||
}
|
||||
|
||||
80% {
|
||||
-webkit-transform: scale3d(.97, .97, .97);
|
||||
transform: scale3d(.97, .97, .97);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
-webkit-transform: scale3d(1, 1, 1);
|
||||
transform: scale3d(1, 1, 1);
|
||||
}
|
||||
}
|
||||
32
src/popup/_buttons.scss
Normal file
32
src/popup/_buttons.scss
Normal file
@@ -0,0 +1,32 @@
|
||||
button {
|
||||
&.btn {
|
||||
display: inline-block;
|
||||
font-weight: 300;
|
||||
line-height: 1.25;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
vertical-align: middle;
|
||||
user-select: none;
|
||||
border: 1px solid transparent;
|
||||
cursor: pointer;
|
||||
letter-spacing: 1px;
|
||||
transition: all .15s ease;
|
||||
|
||||
&.btn-sm {
|
||||
padding: .4rem .8rem;
|
||||
font-size: .8rem;
|
||||
border-radius: .2rem
|
||||
}
|
||||
|
||||
&.btn-primary {
|
||||
color: #fff;
|
||||
background-color: $brand-primary;
|
||||
border-color: $brand-primary;
|
||||
}
|
||||
&.btn-danger {
|
||||
color: #fff;
|
||||
background-color: $brand-danger;
|
||||
border-color: $brand-danger;
|
||||
}
|
||||
}
|
||||
}
|
||||
21
src/popup/_transitions.scss
Normal file
21
src/popup/_transitions.scss
Normal file
@@ -0,0 +1,21 @@
|
||||
// drop down
|
||||
.drop-down-enter, .drop-down-leave-to {
|
||||
transform: translateX(0) translateY(-20px);
|
||||
transition-timing-function: cubic-bezier(.74,.04,.26,1.05);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.drop-down-enter-active, .drop-down-leave-active {
|
||||
transition: all .15s
|
||||
}
|
||||
|
||||
// move left
|
||||
.move-left-enter, .move-left-leave-to {
|
||||
transform: translateY(0) translateX(-80px);
|
||||
transition-timing-function: cubic-bezier(.74,.04,.26,1.05);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.move-left-enter-active, .move-left-leave-active {
|
||||
transition: all .15s
|
||||
}
|
||||
9
src/popup/_typography.scss
Normal file
9
src/popup/_typography.scss
Normal file
@@ -0,0 +1,9 @@
|
||||
.text-muted {
|
||||
color: $gray;
|
||||
}
|
||||
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
34
src/popup/_variables.scss
Normal file
34
src/popup/_variables.scss
Normal file
@@ -0,0 +1,34 @@
|
||||
// spacing
|
||||
$spacer: 12px;
|
||||
|
||||
// colors
|
||||
$white: #fff;
|
||||
$black: #1f2d3d;
|
||||
$red: #FF4949;
|
||||
$orange: #F19B45;
|
||||
$yellow: #FFC82C;
|
||||
$green: #13CE66;
|
||||
|
||||
$blue: #45C8F1;
|
||||
$blue-light: #EBFAFF;
|
||||
$blue-lightest: #F0F8FF;
|
||||
$teal: #205E71;
|
||||
$pink: #FF659D;
|
||||
|
||||
$gray-dark: #3C4858;
|
||||
$gray: #8492A6;
|
||||
$gray-light: #E0E6ED;
|
||||
$gray-lighter: #EFF2F7;
|
||||
$gray-lightest: #F9FAFC;
|
||||
|
||||
$brand-primary: $blue;
|
||||
$brand-success: $green;
|
||||
$brand-info: $teal;
|
||||
$brand-warning: $orange;
|
||||
$brand-danger: $red;
|
||||
$brand-inverse: $gray-dark;
|
||||
$brand-accent: $pink;
|
||||
|
||||
// typography
|
||||
|
||||
$text-muted: $gray;
|
||||
63
src/popup/code-generator/CodeGenerator.js
Normal file
63
src/popup/code-generator/CodeGenerator.js
Normal file
@@ -0,0 +1,63 @@
|
||||
const importPuppeteer = `const puppeteer = require('puppeteer');\n`
|
||||
|
||||
const header = `const browser = await puppeteer.launch()
|
||||
const page = await browser.newPage()`
|
||||
|
||||
const footer = `
|
||||
await browser.close()`
|
||||
|
||||
const wrappedHeader = `$(async () => {
|
||||
const browser = await puppeteer.launch()
|
||||
const page = await browser.newPage()\n`
|
||||
|
||||
const wrappedFooter = `
|
||||
await browser.close()
|
||||
})()`
|
||||
|
||||
const defaults = {
|
||||
asyncWrapper: true
|
||||
}
|
||||
|
||||
export default class CodeGenerator {
|
||||
constructor (options) {
|
||||
this._options = Object.assign(defaults, options)
|
||||
this._header = this._options.asyncWrapper ? wrappedHeader : header
|
||||
this._footer = this._options.asyncWrapper ? wrappedFooter : footer
|
||||
}
|
||||
|
||||
generate (events) {
|
||||
return importPuppeteer + this._header + this._parseEvents(events) + this._footer
|
||||
}
|
||||
|
||||
_parseEvents (events) {
|
||||
console.debug(`generating code for ${events.length} events`)
|
||||
let result = ''
|
||||
for (let event of events) {
|
||||
const { action, url, selector, value, href, keyCode } = event
|
||||
switch (action) {
|
||||
case 'keydown':
|
||||
result += this._handleKeyDown(selector, value, keyCode)
|
||||
break
|
||||
case 'click':
|
||||
result += this._handleClick(selector, href)
|
||||
break
|
||||
case 'goto':
|
||||
result += ` await page.goto('${url}')\n`
|
||||
break
|
||||
case 'reload':
|
||||
result += ` await page.reload()\n`
|
||||
break
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
_handleKeyDown (selector, value, keyCode) {
|
||||
if (keyCode === 9) return ` await page.type('${selector}', '${value}')\n`
|
||||
return ''
|
||||
}
|
||||
|
||||
_handleClick (selector, href) {
|
||||
if (href) return ` await page.goto('${href}')\n`
|
||||
return ` await page.click('${selector}')\n`
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import CodeGenerator from '../CodeGenerator'
|
||||
|
||||
describe('code-generator', () => {
|
||||
test('it should generate nothing when there are no events', () => {
|
||||
const codeGenerator = new CodeGenerator()
|
||||
const events = []
|
||||
expect(codeGenerator._parseEvents(events)).toBeFalsy()
|
||||
})
|
||||
})
|
||||
290
src/popup/components/App.vue
Normal file
290
src/popup/components/App.vue
Normal file
@@ -0,0 +1,290 @@
|
||||
<template>
|
||||
<div id="puppeteer-recorder" class="recorder">
|
||||
<div class="header">
|
||||
Puppeteer recorder
|
||||
<div class="left">
|
||||
<div class="recording-badge" v-show="isRecording">
|
||||
<span class="red-dot"></span>
|
||||
recording
|
||||
</div>
|
||||
<a href="#" @click="openOptions" class="options-button">
|
||||
<img src="/images/settings.svg" alt="settings" width="18px">
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="main">
|
||||
<div class="tabs">
|
||||
<div class="tab record-tab" v-show="!showResultsTab">
|
||||
<div class="content">
|
||||
<div class="empty" v-show="!isRecording">
|
||||
<img src="/images/Desert.svg" alt="desert" width="78px">
|
||||
<h3>No recorded events yet</h3>
|
||||
<p class="text-muted">Click record to begin</p>
|
||||
</div>
|
||||
<div class="events" v-show="isRecording">
|
||||
<p class="text-muted text-center" v-show="liveEvents.length === 0">Waiting for events...</p>
|
||||
<ul class="event-list">
|
||||
<li v-for="(event, index) in liveEvents" class="event-list-item">
|
||||
<div class="event-label">
|
||||
{{index + 1}}.
|
||||
</div>
|
||||
<div class="event-description">
|
||||
<div class="event-action">{{event.action}}</div>
|
||||
<div class="event-props text-muted">{{event.selector || event.href }}</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<button class="btn btn-sm" @click="toggleRecord" :class="isRecording ? 'btn-danger' : 'btn-primary'">
|
||||
{{recordButtonText}}
|
||||
</button>
|
||||
<a href="#" @click="showResultsTab = true" v-show="code">view code</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab results-tab" v-show="showResultsTab">
|
||||
<div class="content">
|
||||
<pre v-show="!code">
|
||||
<code>
|
||||
No code yet...
|
||||
</code>
|
||||
</pre>
|
||||
<pre v-highlightjs="code" v-show="code"><code class="javascript"></code></pre>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<button class="btn btn-sm btn-primary" @click="restart" v-show="code">Restart</button>
|
||||
<a href="#" v-clipboard:copy='code' @click="setCopying" v-show="code">{{copyLinkText}}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CodeGenerator from '../code-generator/CodeGenerator'
|
||||
import './github-gist.css'
|
||||
export default {
|
||||
name: 'App',
|
||||
data () {
|
||||
return {
|
||||
code: '',
|
||||
showResultsTab: false,
|
||||
liveEvents: [],
|
||||
recording: [],
|
||||
isRecording: false,
|
||||
isCopying: false,
|
||||
bus: null
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.loadState(() => {
|
||||
if (this.isRecording) {
|
||||
console.debug('opened in recording state, fetching recording events')
|
||||
chrome.storage.local.get(['recording', 'code'], ({ recording }) => {
|
||||
console.debug('loaded recording', recording)
|
||||
this.liveEvents = recording
|
||||
})
|
||||
}
|
||||
})
|
||||
this.bus = chrome.extension.connect({ name: 'recordControls' })
|
||||
},
|
||||
methods: {
|
||||
toggleRecord () {
|
||||
if (this.isRecording) {
|
||||
this.stop()
|
||||
} else {
|
||||
this.start()
|
||||
}
|
||||
this.isRecording = !this.isRecording
|
||||
this.storeState()
|
||||
},
|
||||
start () {
|
||||
console.debug('start recorder')
|
||||
this.code = ''
|
||||
this.bus.postMessage({ action: 'start' })
|
||||
},
|
||||
stop () {
|
||||
console.debug('stop recorder')
|
||||
this.bus.postMessage({ action: 'stop' })
|
||||
|
||||
chrome.storage.local.get(['recording', 'codeOptions'], ({ recording, codeOptions }) => {
|
||||
console.debug('loaded recording', recording)
|
||||
this.recording = recording
|
||||
const codeGen = new CodeGenerator(codeOptions)
|
||||
this.code = codeGen.generate(this.recording)
|
||||
this.showResultsTab = true
|
||||
this.storeState()
|
||||
})
|
||||
},
|
||||
restart () {
|
||||
console.log('restart')
|
||||
this.bus.postMessage({ action: 'restart' })
|
||||
|
||||
this.recording = this.liveEvents = []
|
||||
this.code = ''
|
||||
this.showResultsTab = false
|
||||
this.storeState()
|
||||
},
|
||||
openOptions () {
|
||||
if (chrome.runtime.openOptionsPage) {
|
||||
chrome.runtime.openOptionsPage()
|
||||
}
|
||||
},
|
||||
loadState (cb) {
|
||||
chrome.storage.local.get(['controls', 'code'], ({ controls, code }) => {
|
||||
console.debug('loaded controls', controls)
|
||||
if (controls) {
|
||||
this.isRecording = controls.isRecording
|
||||
}
|
||||
|
||||
if (code) {
|
||||
this.code = code
|
||||
}
|
||||
cb()
|
||||
})
|
||||
},
|
||||
storeState () {
|
||||
chrome.storage.local.set({
|
||||
code: this.code,
|
||||
controls: {
|
||||
isRecording: this.isRecording
|
||||
}
|
||||
})
|
||||
},
|
||||
setCopying () {
|
||||
this.isCopying = true
|
||||
setTimeout(() => { this.isCopying = false }, 1500)
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
recordButtonText () {
|
||||
return this.isRecording ? 'Stop' : 'Record'
|
||||
},
|
||||
copyLinkText () {
|
||||
return this.isCopying ? 'copied!' : 'copy to clipboard'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../variables";
|
||||
|
||||
.recorder {
|
||||
font-size: 14px;
|
||||
|
||||
.header {
|
||||
background: $gray-lightest;
|
||||
height: 48px;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
padding: 0 $spacer;
|
||||
font-weight: 500;
|
||||
|
||||
.left {
|
||||
margin-left: auto;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
|
||||
.recording-badge {
|
||||
color: $brand-danger;
|
||||
.red-dot {
|
||||
height: 8px;
|
||||
width: 8px;
|
||||
background-color: $brand-danger;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
margin-right: .4rem;
|
||||
vertical-align: middle;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
|
||||
.options-button {
|
||||
margin-left: $spacer;
|
||||
img {
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.record-tab {
|
||||
.content {
|
||||
min-height: 200px;
|
||||
.empty {
|
||||
padding: $spacer;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.events {
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
|
||||
.event-list {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
.event-list-item {
|
||||
padding: 12px;
|
||||
font-size: 12px;
|
||||
border-top: 1px solid $gray-light;
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
height: 32px;
|
||||
|
||||
.event-label {
|
||||
vertical-align: top;
|
||||
margin-right: $spacer;
|
||||
}
|
||||
|
||||
.event-description {
|
||||
margin-right: auto;
|
||||
display: inline-block;
|
||||
|
||||
.event-action {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.event-props {
|
||||
white-space: pre;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.results-tab {
|
||||
.content {
|
||||
pre {
|
||||
padding: 0 $spacer;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.code {
|
||||
font-family: Consolas, Monaco, monospace;
|
||||
padding: $spacer;
|
||||
}
|
||||
|
||||
.footer {
|
||||
background: $gray-lightest;
|
||||
height: 60px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0 $spacer;
|
||||
font-weight: 500;
|
||||
border-top: 1px solid $gray-light;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
71
src/popup/components/github-gist.css
Normal file
71
src/popup/components/github-gist.css
Normal file
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* GitHub Gist Theme
|
||||
* Author : Louis Barranqueiro - https://github.com/LouisBarranqueiro
|
||||
*/
|
||||
|
||||
.hljs {
|
||||
display: block;
|
||||
background: white;
|
||||
padding: 0.5em;
|
||||
color: #333333;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.hljs-comment,
|
||||
.hljs-meta {
|
||||
color: #969896;
|
||||
}
|
||||
|
||||
.hljs-string,
|
||||
.hljs-variable,
|
||||
.hljs-template-variable,
|
||||
.hljs-strong,
|
||||
.hljs-emphasis,
|
||||
.hljs-quote {
|
||||
color: #df5000;
|
||||
}
|
||||
|
||||
.hljs-keyword,
|
||||
.hljs-selector-tag,
|
||||
.hljs-type {
|
||||
color: #a71d5d;
|
||||
}
|
||||
|
||||
.hljs-literal,
|
||||
.hljs-symbol,
|
||||
.hljs-bullet,
|
||||
.hljs-attribute {
|
||||
color: #0086b3;
|
||||
}
|
||||
|
||||
.hljs-section,
|
||||
.hljs-name {
|
||||
color: #63a35c;
|
||||
}
|
||||
|
||||
.hljs-tag {
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.hljs-title,
|
||||
.hljs-attr,
|
||||
.hljs-selector-id,
|
||||
.hljs-selector-class,
|
||||
.hljs-selector-attr,
|
||||
.hljs-selector-pseudo {
|
||||
color: #795da3;
|
||||
}
|
||||
|
||||
.hljs-addition {
|
||||
color: #55a532;
|
||||
background-color: #eaffea;
|
||||
}
|
||||
|
||||
.hljs-deletion {
|
||||
color: #bd2c00;
|
||||
background-color: #ffecec;
|
||||
}
|
||||
|
||||
.hljs-link {
|
||||
text-decoration: underline;
|
||||
}
|
||||
15
src/popup/index.js
Normal file
15
src/popup/index.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import Vue from 'vue'
|
||||
import VueHighlightJS from 'vue-highlightjs'
|
||||
import VueClipboard from 'vue-clipboard2'
|
||||
import App from './components/App.vue'
|
||||
import './style.scss'
|
||||
|
||||
Vue.config.productionTip = false
|
||||
Vue.use(VueHighlightJS)
|
||||
Vue.use(VueClipboard)
|
||||
|
||||
/* eslint-disable no-new */
|
||||
new Vue({
|
||||
el: '#root',
|
||||
render: h => h(App)
|
||||
})
|
||||
23
src/popup/style.scss
Normal file
23
src/popup/style.scss
Normal file
@@ -0,0 +1,23 @@
|
||||
@import "variables";
|
||||
@import "buttons";
|
||||
@import "typography";
|
||||
@import "transitions";
|
||||
@import "animations";
|
||||
|
||||
//reset
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-size: 100%;
|
||||
color: $gray-dark;
|
||||
width: 350px;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: $brand-primary;
|
||||
}
|
||||
|
||||
h1, h2, h3 {
|
||||
margin-top: 0;
|
||||
}
|
||||
10
src/popup/template.html
Normal file
10
src/popup/template.html
Normal file
@@ -0,0 +1,10 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset='utf-8'>
|
||||
</head>
|
||||
<body>
|
||||
<div id='root'></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
</html>
|
||||
107
webpack.config.babel.js
Normal file
107
webpack.config.babel.js
Normal file
@@ -0,0 +1,107 @@
|
||||
import webpack from 'webpack'
|
||||
import HtmlWebpackPlugin from 'html-webpack-plugin'
|
||||
import ChromeExtensionReloader from 'webpack-chrome-extension-reloader'
|
||||
import CopyPlugin from 'copy-webpack-plugin'
|
||||
import { VueLoaderPlugin } from 'vue-loader'
|
||||
import path from 'path'
|
||||
|
||||
const { NODE_ENV = 'development' } = process.env
|
||||
|
||||
const base = {
|
||||
context: __dirname,
|
||||
entry: {
|
||||
background: './src/background/index.js',
|
||||
'content-script': './src/content-scripts/index.js',
|
||||
popup: './src/popup/index.js',
|
||||
options: './src/options/options.js'
|
||||
},
|
||||
output: {
|
||||
path: path.join(__dirname, 'build'),
|
||||
filename: '[name].js'
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
exclude: /node_modules/,
|
||||
loader: 'babel-loader'
|
||||
},
|
||||
{
|
||||
test: /\.vue$/,
|
||||
loader: 'vue-loader'
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: [
|
||||
'vue-style-loader',
|
||||
'css-loader'
|
||||
]
|
||||
},
|
||||
{
|
||||
test: /\.scss$/,
|
||||
use: [
|
||||
{
|
||||
loader: 'style-loader' // creates style nodes from JS strings
|
||||
},
|
||||
{
|
||||
loader: 'css-loader' // translates CSS into CommonJS
|
||||
},
|
||||
{
|
||||
loader: 'sass-loader' // compiles Sass to CSS
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
new CopyPlugin([
|
||||
{ from: './src/manifest.json', to: './manifest.json' },
|
||||
{ from: './src/options/options.html', to: './options.html' },
|
||||
{ from: './src/images', to: 'images' }
|
||||
]),
|
||||
new HtmlWebpackPlugin({
|
||||
template: './src/popup/template.html',
|
||||
chunks: ['popup']
|
||||
}),
|
||||
new VueLoaderPlugin(),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {
|
||||
NODE_ENV: JSON.stringify(NODE_ENV)
|
||||
}
|
||||
})
|
||||
]
|
||||
}
|
||||
|
||||
const development = {
|
||||
...base,
|
||||
mode: 'development',
|
||||
devtool: '#eval-module-source-map',
|
||||
module: {
|
||||
...base.module
|
||||
},
|
||||
plugins: [
|
||||
...base.plugins,
|
||||
new webpack.HotModuleReplacementPlugin()
|
||||
// new ChromeExtensionReloader()
|
||||
]
|
||||
}
|
||||
|
||||
const production = {
|
||||
...base,
|
||||
mode: 'production',
|
||||
devtool: '#source-map',
|
||||
|
||||
plugins: [
|
||||
...base.plugins,
|
||||
new webpack.LoaderOptionsPlugin({
|
||||
minimize: true,
|
||||
debug: false
|
||||
})
|
||||
]
|
||||
}
|
||||
|
||||
if (NODE_ENV === 'development') {
|
||||
module.exports = development
|
||||
} else {
|
||||
module.exports = production
|
||||
}
|
||||
Reference in New Issue
Block a user