From 24c6a4dfb88416f1389bdb69ee69613639f7389a Mon Sep 17 00:00:00 2001 From: Michael Goeke Date: Thu, 16 Jan 2025 12:12:36 -0800 Subject: [PATCH] ui facelift, many minor fixes --- package.json | 2 +- src/browser/PW_live.css | 1 - src/browser/PW_live.js | 90 +++++++++++++++++++++++++++++++---------- src/main.ts | 4 ++ src/pageObjectModel.ts | 26 ++++++------ 5 files changed, 88 insertions(+), 35 deletions(-) diff --git a/package.json b/package.json index a7fc7b2..5feaf37 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@dnvgl/playwright-live-recorder", "type": "commonjs", - "version": "2.0.80.dev", + "version": "2.0.50", "description": "Adds live coding in testing context from browser console when running tests. Provides programmatically configurable recorder. Introduces the idea of strong convention for Page Object Model", "main": "dist/main.js", "files": [ diff --git a/src/browser/PW_live.css b/src/browser/PW_live.css index 7220234..9f36478 100644 --- a/src/browser/PW_live.css +++ b/src/browser/PW_live.css @@ -2,7 +2,6 @@ position: sticky; z-index: 2147483647; top: 0; - border-bottom: 1px solid black; } .PW-tooltip { diff --git a/src/browser/PW_live.js b/src/browser/PW_live.js index 646139d..241a3b5 100644 --- a/src/browser/PW_live.js +++ b/src/browser/PW_live.js @@ -5,28 +5,29 @@ window.PW_statusbar = document.createElement("div"); PW_statusbar.classList.add("PW"); PW_statusbar.innerHTML = ` -
- - - - - -
-
-
- - -
-
+
+
+
+ ⦙⦙ + Playwright Live Recorder + + +
+
+
+
+
+ + +
+
+
`; document.body.prepend(PW_statusbar); -window.PW_repl = document.getElementById("PW-repl"); -PW_repl.addEventListener("keyup", (event) => ((event.code || event.key) === "Enter" ? PW_updateAndRerunLastCommand(PW_repl.value) : {})); - window.PW_eval_error = document.getElementById("PW-eval-error"); PW_eval_error.style.display = "none"; window.PW_eval_error_summary = document.getElementById("PW-eval-error-summary"); @@ -34,6 +35,41 @@ window.PW_eval_error_details = document.getElementById("PW-eval-error-details"); window.PW_page_object_model_filename = document.getElementById("PW-page-object-model-filename"); + +var PLR_dragElement = document.getElementById('PW-drag-element'); +var PLR_statusBar = document.getElementById('PW-statusbar'); + +var _pw_drag_startX, _pw_drag_startY, pw_drag_initialTransformX; + +PLR_dragElement.addEventListener('mousedown', (event) => { + const tx = PLR_statusBar.style.transform; + pw_drag_initialTransformX = parseInt(tx.substring(tx.indexOf('(') + 1, tx.indexOf('px')), 10); + if (isNaN(pw_drag_initialTransformX)) pw_drag_initialTransformX = 0; + + _pw_drag_startX = event.clientX; + _pw_drag_startY = event.clientY; + + document.addEventListener('mousemove', onMouseMove); + document.addEventListener('mouseup', onMouseUp); + + PLR_dragElement.style.cursor = 'grabbing'; +}); + +function onMouseMove(event) { + console.log('mouseMove'); + const currentX = event.clientX; + const deltaX = currentX - _pw_drag_startX; + PLR_statusBar.style.transform = `translateX(${deltaX + pw_drag_initialTransformX}px)`; +} + +function onMouseUp() { + document.removeEventListener('mousemove', onMouseMove); + document.removeEventListener('mouseup', onMouseUp); + + PLR_dragElement.style.cursor = 'grab'; +} + + if (window.PW_tooltip) PW_tooltip.remove(); var PW_tooltip = document.createElement("div"); PW_tooltip.setAttribute("id", "PW_tooltip"); @@ -138,7 +174,7 @@ function mousemove_updateTooltip(event) { } } -async function recordModeClickHandler(event) { +async function recordModeClickHandler_swallowClick(event) { if (!recordModeOn) return; if (window.PW_executing) return; @@ -148,7 +184,13 @@ async function recordModeClickHandler(event) { event.preventDefault(); event.stopImmediatePropagation(); + return element; +} +async function recordModeClickHandler(event) { + const element = await recordModeClickHandler_swallowClick(event); + if (element == null) return; + let newItemName; const selectorConvention = document.PW_getSelectorConventionForElement(element); if (config.pageObjectModel.enabled && !selectorConvention.isPageObjectModel) { @@ -156,6 +198,9 @@ async function recordModeClickHandler(event) { if (newItemName != null) { const selector = selectorConvention.match(element); await PW_appendToPageObjectModel(pageObjectFilePath, config.pageObjectModel.generatePropertyTemplate(newItemName, selector)); + } else { + const selector = selectorConvention.match(element); + navigator.clipboard.writeText(selector); } return; } @@ -164,8 +209,6 @@ async function recordModeClickHandler(event) { const primaryAction = element.closest('[data-page-object-model-primary-action]').getAttribute("data-page-object-model-primary-action"); //todo - implement secondary actions const replLine = primaryAction.replaceAll('$1', resultOutput); - PW_repl.value = replLine; - PW_repl.disabled = false; if (selectorConvention.isPageObjectModel) { await PW_appendToTest(replLine, element.closest('[data-page-object-model-import]').getAttribute("data-page-object-model-import")); @@ -181,6 +224,11 @@ document.PW_getSelectorConventionForElement = function (el) { window.addEventListener("keydown", keyChord_toggleRecordMode); window.addEventListener("mousemove", mousemove_updateTooltip); window.addEventListener("click", recordModeClickHandler, true); +//todo - figure out how to capture click on disabled elements +//var _PW_mousedown_element; +//window.addEventListener('pointerdown', function (e) { _PW_mousedown_element = e.target; recordModeClickHandler_swallowClick(e);});//, true); +//document.addEventListener('pointerup', function (e) { if (e.target === _PW_mousedown_element) recordModeClickHandler(e); });//, true); + /******** page object model feature ********/ @@ -195,7 +243,7 @@ async function reload_page_object_model_elements() { //get current page object to reflect across pageObjectFilePath = await PW_urlToFilePath(window.location.href); - PW_page_object_model_filename.value = pageObjectFilePath; + PW_page_object_model_filename.innerText = pageObjectFilePath ?? "Playwright Live Recorder"; if (!recordModeOn) return; diff --git a/src/main.ts b/src/main.ts index 29ed6a5..3da9157 100644 --- a/src/main.ts +++ b/src/main.ts @@ -119,6 +119,10 @@ export class ${className} { * @param evalScope pass value of `s => eval(s)`, this provides the test's execution scope so eval'd lines have local scope variables, relative import paths, etc */ export async function start(page: Page, evalScope: (s: string) => any) { + if (evalScope.toString() !== 's => eval(s)'){ + const testEval = evalScope('1+1'); + if (testEval !== 2) throw new Error(`evalScope does not evaluate correctly, please verify, should look something like \`s => eval(s)\`, instead got \`${evalScope.toString()}\``); + } const pageState = page; if (pageState.PlaywrightLiveRecorder_started === true) { return; diff --git a/src/pageObjectModel.ts b/src/pageObjectModel.ts index d556a41..a3c0743 100644 --- a/src/pageObjectModel.ts +++ b/src/pageObjectModel.ts @@ -23,18 +23,7 @@ export module pageObjectModel { export async function init(testFileDir: string, config: PlaywrightLiveRecorderConfig_pageObjectModel, evalScope: (s: string) => any, page: Page) { _state = {testFileDir, config, evalScope, page}; - await page.exposeFunction('PW_urlToFilePath', async (url: string) => { - const newfilePath = config.urlToFilePath(url, config.aliases); - if (newfilePath === currentPageFilePath) return currentPageFilePath; - currentPageFilePath = newfilePath; - - await currentPageFilePathWatcher?.close(); - currentPageFilePathWatcher = chokidar.watch(currentPageFilePath, { cwd: config.path }) - .on( 'add', /*path*/() => reload(page)) - .on('change', /*path*/() => reload(page)); - - return currentPageFilePath; - }); + await page.exposeFunction('PW_urlToFilePath', async (url: string) => PW_urlToFilePath(url)); await page.exposeFunction('PW_importStatement', (className: string, pathFromRoot: string) => _importStatement(className, nodePath.join(process.cwd(), _state.config.path, pathFromRoot), _state.testFileDir)); @@ -42,6 +31,19 @@ export module pageObjectModel { await page.exposeFunction('PW_appendToPageObjectModel', (path: string, codeBlock: string) => _appendToPageObjectModel(fullRelativePath(path, config), classNameFromPath(path), codeBlock, config)); } + export async function PW_urlToFilePath(url: string) { + const newfilePath = _state.config.urlToFilePath(url, _state.config.aliases); + if (newfilePath === currentPageFilePath) return currentPageFilePath; + currentPageFilePath = newfilePath; + + await currentPageFilePathWatcher?.close(); + const cwd = nodePath.join(process.cwd(), _state.config.path); + currentPageFilePathWatcher = chokidar.watch(currentPageFilePath, { cwd }) + .on( 'add', /*path*/() => reload(_state.page)) + .on('change', /*path*/() => reload(_state.page)); + + return currentPageFilePath; +} export function _importStatement(className: string, pathFromRoot: string, testFileDir: string) { const x = nodePath.parse(nodePath.relative(testFileDir, pathFromRoot)); let importPath = nodePath.join(x.dir, x.name).replaceAll('\\', '/'); // relative path without extension