diff --git a/package-lock.json b/package-lock.json index 325ada8095..0d7725e9e8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2283,10 +2283,10 @@ "mimic-response": "^1.0.0" } }, - "node_modules/codemirror": { - "version": "5.65.9", - "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.65.9.tgz", - "integrity": "sha512-19Jox5sAKpusTDgqgKB5dawPpQcY+ipQK7xoEI+MVucEF9qqFaXpeqY1KaoyGBso/wHQoDa4HMMxMjdsS3Zzzw==" + "node_modules/codemirror-shadow-1": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/codemirror-shadow-1/-/codemirror-shadow-1-0.0.1.tgz", + "integrity": "sha512-kD3OZpCCHr3LHRKfbGx5IogHTWq4Uo9jH2bXPVa7/n6ppkgI66rx4tniQY1BpqWp/JNhQmQsXhQoaZ1TH6t0xQ==" }, "node_modules/color-convert": { "version": "1.9.3", @@ -7310,7 +7310,7 @@ "packages/web": { "version": "0.0.0", "dependencies": { - "codemirror": "^5.65.9", + "codemirror-shadow-1": "0.0.1", "xterm": "^5.1.0", "xterm-addon-fit": "^0.7.0" } @@ -8962,10 +8962,10 @@ "mimic-response": "^1.0.0" } }, - "codemirror": { - "version": "5.65.9", - "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.65.9.tgz", - "integrity": "sha512-19Jox5sAKpusTDgqgKB5dawPpQcY+ipQK7xoEI+MVucEF9qqFaXpeqY1KaoyGBso/wHQoDa4HMMxMjdsS3Zzzw==" + "codemirror-shadow-1": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/codemirror-shadow-1/-/codemirror-shadow-1-0.0.1.tgz", + "integrity": "sha512-kD3OZpCCHr3LHRKfbGx5IogHTWq4Uo9jH2bXPVa7/n6ppkgI66rx4tniQY1BpqWp/JNhQmQsXhQoaZ1TH6t0xQ==" }, "color-convert": { "version": "1.9.3", @@ -11862,7 +11862,7 @@ "web": { "version": "file:packages/web", "requires": { - "codemirror": "^5.65.9", + "codemirror-shadow-1": "0.0.1", "xterm": "^5.1.0", "xterm-addon-fit": "^0.7.0" } diff --git a/packages/playwright-core/ThirdPartyNotices.txt b/packages/playwright-core/ThirdPartyNotices.txt index e2c4d1dcac..f18b4af05a 100644 --- a/packages/playwright-core/ThirdPartyNotices.txt +++ b/packages/playwright-core/ThirdPartyNotices.txt @@ -10,7 +10,7 @@ This project incorporates components from the projects listed below. The origina - balanced-match@1.0.2 (https://github.com/juliangruber/balanced-match) - brace-expansion@1.1.11 (https://github.com/juliangruber/brace-expansion) - buffer-crc32@0.2.13 (https://github.com/brianloveswords/buffer-crc32) -- codemirror@5.65.9 (https://github.com/codemirror/CodeMirror) +- codemirror-shadow-1@0.0.1 (https://github.com/codemirror/CodeMirror) - colors@1.4.0 (https://github.com/Marak/colors.js) - commander@8.3.0 (https://github.com/tj/commander.js) - concat-map@0.0.1 (https://github.com/substack/node-concat-map) @@ -326,11 +326,11 @@ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEAL ========================================= END OF buffer-crc32@0.2.13 AND INFORMATION -%% codemirror@5.65.9 NOTICES AND INFORMATION BEGIN HERE +%% codemirror-shadow-1@0.0.1 NOTICES AND INFORMATION BEGIN HERE ========================================= MIT License -Copyright (C) 2017 by Marijn Haverbeke and others +Copyright (C) 2017 by Marijn Haverbeke and others Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -350,7 +350,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ========================================= -END OF codemirror@5.65.9 AND INFORMATION +END OF codemirror-shadow-1@0.0.1 AND INFORMATION %% colors@1.4.0 NOTICES AND INFORMATION BEGIN HERE ========================================= diff --git a/packages/playwright-core/src/server/injected/highlight.css b/packages/playwright-core/src/server/injected/highlight.css index 0d0a74e7b3..f4db846697 100644 --- a/packages/playwright-core/src/server/injected/highlight.css +++ b/packages/playwright-core/src/server/injected/highlight.css @@ -44,8 +44,9 @@ x-pw-dialog { display: flex; flex-direction: column; position: absolute; - min-width: 500px; - min-height: 200px; + width: 500px; + height: 200px; + z-index: 10; } x-pw-dialog-body { @@ -54,6 +55,17 @@ x-pw-dialog-body { flex: auto; } +x-pw-dialog-body label { + margin: 10px; + display: flex; + align-items: center; + cursor: pointer; +} + +x-pw-dialog-body input { + cursor: pointer; +} + x-pw-highlight { position: absolute; top: 0; @@ -205,27 +217,17 @@ x-pw-overlay x-pw-tool-item { margin: 2px; } -input.locator-editor { - display: flex; - padding: 10px; - flex: none; - border: none; - border-bottom: 1px solid #dddddd; -} - -input.locator-editor:focus, -textarea.text-editor:focus { - outline: none; -} - textarea.text-editor { font-family: system-ui, "Ubuntu", "Droid Sans", sans-serif; flex: auto; border: none; - padding: 10px; + margin: 10px; color: #333; } +textarea.text-editor:focus { + outline: none; +} x-div { display: block; @@ -242,3 +244,16 @@ x-spacer { *[hidden] { display: none !important; } + +x-locator-editor { + flex: none; + width: 100%; + height: 60px; + padding: 4px; + border-bottom: 1px solid #dddddd; +} + +.CodeMirror { + width: 100% !important; + height: 100% !important; +} diff --git a/packages/playwright-core/src/server/injected/highlight.ts b/packages/playwright-core/src/server/injected/highlight.ts index c629bf092b..b9b8739656 100644 --- a/packages/playwright-core/src/server/injected/highlight.ts +++ b/packages/playwright-core/src/server/injected/highlight.ts @@ -60,7 +60,7 @@ export class Highlight { this._glassPaneElement.style.pointerEvents = 'none'; this._glassPaneElement.style.display = 'flex'; this._glassPaneElement.style.backgroundColor = 'transparent'; - for (const eventName of ['click', 'auxclick', 'dragstart', 'input', 'keydown', 'keyup', 'pointerdown', 'pointerup', 'mousedown', 'mouseup', 'mousemove', 'mouseleave', 'focus', 'scroll']) { + for (const eventName of ['click', 'auxclick', 'dragstart', 'input', 'keydown', 'keyup', 'pointerdown', 'pointerup', 'mousedown', 'mouseup', 'mouseleave', 'focus', 'scroll']) { this._glassPaneElement.addEventListener(eventName, e => { e.stopPropagation(); e.stopImmediatePropagation(); diff --git a/packages/playwright-core/src/server/injected/recorder.ts b/packages/playwright-core/src/server/injected/recorder.ts index 3ab0845ea6..d98b072907 100644 --- a/packages/playwright-core/src/server/injected/recorder.ts +++ b/packages/playwright-core/src/server/injected/recorder.ts @@ -23,10 +23,28 @@ import { Highlight, type HighlightOptions } from '../injected/highlight'; import { isInsideScope } from './domUtils'; import { elementText } from './selectorUtils'; import { asLocator } from '../../utils/isomorphic/locatorGenerators'; +import type { Language } from '../../utils/isomorphic/locatorGenerators'; import { locatorOrSelectorAsSelector } from '@isomorphic/locatorParser'; import { parseSelector } from '@isomorphic/selectorParser'; import { normalizeWhiteSpace } from '@isomorphic/stringUtils'; +// @ts-ignore @no-check-deps +import CodeMirrorImpl from 'codemirror-shadow-1'; +import type CodeMirrorType from 'codemirror'; +// @no-check-deps +import codemirrorCSS from 'codemirror-shadow-1/lib/codemirror.css?inline'; +// @no-check-deps +import 'codemirror-shadow-1/mode/css/css'; +// @no-check-deps +import 'codemirror-shadow-1/mode/htmlmixed/htmlmixed'; +// @no-check-deps +import 'codemirror-shadow-1/mode/javascript/javascript'; +// @no-check-deps +import 'codemirror-shadow-1/mode/python/python'; +// @no-check-deps +import 'codemirror-shadow-1/mode/clike/clike'; +const CodeMirror = CodeMirrorImpl as typeof CodeMirrorType; + interface RecorderDelegate { performAction?(action: actions.Action): Promise; recordAction?(action: actions.Action): Promise; @@ -507,7 +525,7 @@ class TextAssertionTool implements RecorderTool { selector, signals: [], // Interestingly, inputElement.checked is reversed inside this event handler. - checked: (target as HTMLInputElement).checked, + checked: !(target as HTMLInputElement).checked, }; } else { return { @@ -576,12 +594,21 @@ class TextAssertionTool implements RecorderTool { this._dialogElement.appendChild(toolbarElement); const bodyElement = this._recorder.document.createElement('x-pw-dialog-body'); - const locatorElement = this._recorder.document.createElement('input'); - locatorElement.classList.add('locator-editor'); - locatorElement.value = asLocator(this._recorder.state.language, this._action.selector); - locatorElement.addEventListener('input', () => { + const cmStyle = this._recorder.document.createElement('style'); + const cmElement = this._recorder.document.createElement('x-locator-editor'); + cmStyle.textContent = codemirrorCSS; + bodyElement.appendChild(cmStyle); + bodyElement.appendChild(cmElement); + const cm = CodeMirror(cmElement, { + value: asLocator(this._recorder.state.language, this._action.selector), + mode: cmModeForLanguage(this._recorder.state.language), + readOnly: false, + lineNumbers: false, + lineWrapping: true, + }); + cm.on('change', () => { if (this._action) { - const selector = locatorOrSelectorAsSelector(this._recorder.state.language, locatorElement.value, this._recorder.state.testIdAttributeName); + const selector = locatorOrSelectorAsSelector(this._recorder.state.language, cm.getValue(), this._recorder.state.testIdAttributeName); const model: HighlightModel = { selector, elements: this._recorder.injectedScript.querySelectorAll(parseSelector(selector), this._recorder.document), @@ -590,27 +617,46 @@ class TextAssertionTool implements RecorderTool { this._recorder.updateHighlight(model, true); } }); - const textElement = this._recorder.document.createElement('textarea'); - textElement.value = this._renderValue(this._action); - textElement.classList.add('text-editor'); - textElement.addEventListener('input', () => { - if (this._action?.name === 'assertText') - this._action.text = normalizeWhiteSpace(elementText(new Map(), textElement).full); - if (this._action?.name === 'assertChecked') - this._action.checked = textElement.value === 'true'; - if (this._action?.name === 'assertValue') - this._action.value = textElement.value; - }); + let elementToFocus: HTMLElement | null = null; + if (this._action.name !== 'assertChecked') { + const textElement = this._recorder.document.createElement('textarea'); + textElement.setAttribute('spellcheck', 'false'); + textElement.value = this._renderValue(this._action); + textElement.classList.add('text-editor'); + + textElement.addEventListener('input', () => { + if (this._action?.name === 'assertText') + this._action.text = normalizeWhiteSpace(elementText(new Map(), textElement).full); + if (this._action?.name === 'assertChecked') + this._action.checked = textElement.value === 'true'; + if (this._action?.name === 'assertValue') + this._action.value = textElement.value; + }); + bodyElement.appendChild(textElement); + elementToFocus = textElement; + } else { + const labelElement = this._recorder.document.createElement('label'); + labelElement.textContent = 'Value:'; + const checkboxElement = this._recorder.document.createElement('input'); + labelElement.appendChild(checkboxElement); + checkboxElement.type = 'checkbox'; + checkboxElement.checked = this._action.checked; + checkboxElement.addEventListener('change', () => { + if (this._action?.name === 'assertChecked') + this._action.checked = checkboxElement.checked; + }); + bodyElement.appendChild(labelElement); + elementToFocus = labelElement; + } - bodyElement.appendChild(locatorElement); - bodyElement.appendChild(textElement); this._dialogElement.appendChild(bodyElement); this._recorder.highlight.appendChild(this._dialogElement); const position = this._recorder.highlight.tooltipPosition(this._recorder.highlight.firstBox()!, this._dialogElement); this._dialogElement.style.top = position.anchorTop + 'px'; this._dialogElement.style.left = position.anchorLeft + 'px'; - textElement.focus(); + elementToFocus?.focus(); + cm.refresh(); } private _createLabel(action: actions.AssertAction) { @@ -1131,4 +1177,14 @@ export class PollingRecorder implements RecorderDelegate { } } +function cmModeForLanguage(language: Language): string { + if (language === 'python') + return 'python'; + if (language === 'java') + return 'text/x-java'; + if (language === 'csharp') + return 'text/x-csharp'; + return 'javascript'; +} + export default PollingRecorder; diff --git a/packages/playwright/ThirdPartyNotices.txt b/packages/playwright/ThirdPartyNotices.txt index 3adafd64c3..32ee414789 100644 --- a/packages/playwright/ThirdPartyNotices.txt +++ b/packages/playwright/ThirdPartyNotices.txt @@ -99,7 +99,7 @@ This project incorporates components from the projects listed below. The origina - chalk@4.1.2 (https://github.com/chalk/chalk) - chokidar@3.5.3 (https://github.com/paulmillr/chokidar) - ci-info@3.8.0 (https://github.com/watson/ci-info) -- codemirror@5.65.9 (https://github.com/codemirror/CodeMirror) +- codemirror-shadow-1@0.0.1 (https://github.com/codemirror/CodeMirror) - color-convert@1.9.3 (https://github.com/Qix-/color-convert) - color-convert@2.0.1 (https://github.com/Qix-/color-convert) - color-name@1.1.3 (https://github.com/dfcreative/color-name) @@ -3156,11 +3156,11 @@ SOFTWARE. ========================================= END OF ci-info@3.8.0 AND INFORMATION -%% codemirror@5.65.9 NOTICES AND INFORMATION BEGIN HERE +%% codemirror-shadow-1@0.0.1 NOTICES AND INFORMATION BEGIN HERE ========================================= MIT License -Copyright (C) 2017 by Marijn Haverbeke and others +Copyright (C) 2017 by Marijn Haverbeke and others Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -3180,7 +3180,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ========================================= -END OF codemirror@5.65.9 AND INFORMATION +END OF codemirror-shadow-1@0.0.1 AND INFORMATION %% color-convert@1.9.3 NOTICES AND INFORMATION BEGIN HERE ========================================= diff --git a/packages/web/package.json b/packages/web/package.json index 2b21846455..3898ac1bbb 100644 --- a/packages/web/package.json +++ b/packages/web/package.json @@ -4,7 +4,7 @@ "version": "0.0.0", "scripts": {}, "dependencies": { - "codemirror": "^5.65.9", + "codemirror-shadow-1": "0.0.1", "xterm": "^5.1.0", "xterm-addon-fit": "^0.7.0" } diff --git a/packages/web/src/components/codeMirrorModule.tsx b/packages/web/src/components/codeMirrorModule.tsx index 9009eface0..56686c45b6 100644 --- a/packages/web/src/components/codeMirrorModule.tsx +++ b/packages/web/src/components/codeMirrorModule.tsx @@ -14,13 +14,15 @@ limitations under the License. */ -import codemirror from 'codemirror'; -import 'codemirror/lib/codemirror.css'; -import 'codemirror/mode/css/css'; -import 'codemirror/mode/htmlmixed/htmlmixed'; -import 'codemirror/mode/javascript/javascript'; -import 'codemirror/mode/python/python'; -import 'codemirror/mode/clike/clike'; +// @ts-ignore +import codemirror from 'codemirror-shadow-1'; +import type codemirrorType from 'codemirror'; +import 'codemirror-shadow-1/lib/codemirror.css'; +import 'codemirror-shadow-1/mode/css/css'; +import 'codemirror-shadow-1/mode/htmlmixed/htmlmixed'; +import 'codemirror-shadow-1/mode/javascript/javascript'; +import 'codemirror-shadow-1/mode/python/python'; +import 'codemirror-shadow-1/mode/clike/clike'; -export type CodeMirror = typeof codemirror; +export type CodeMirror = typeof codemirrorType; export default codemirror; diff --git a/utils/check_deps.js b/utils/check_deps.js index ab1ed12072..fcfb278a18 100644 --- a/utils/check_deps.js +++ b/utils/check_deps.js @@ -79,7 +79,7 @@ async function innerCheckDeps(root) { }); const sourceFiles = program.getSourceFiles(); const errors = []; - sourceFiles.filter(x => !x.fileName.includes('node_modules')).map(x => visit(x, x.fileName)); + sourceFiles.filter(x => !x.fileName.includes('node_modules')).map(x => visit(x, x.fileName, x.getFullText())); if (errors.length) { for (const error of errors) @@ -112,7 +112,7 @@ async function innerCheckDeps(root) { return packageJSON; - function visit(node, fileName) { + function visit(node, fileName, text) { if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) { if (node.importClause) { if (node.importClause.isTypeOnly) @@ -151,6 +151,14 @@ async function innerCheckDeps(root) { return; } + const fullStart = node.getFullStart(); + const commentRanges = ts.getLeadingCommentRanges(text, fullStart); + for (const range of commentRanges || []) { + const comment = text.substring(range.pos, range.end); + if (comment.includes('@no-check-deps')) + return; + } + if (importName.startsWith('@')) deps.add(importName.split('/').slice(0, 2).join('/')); else @@ -159,7 +167,7 @@ async function innerCheckDeps(root) { if (!allowExternalImport(importName, packageJSON)) errors.push(`Disallowed external dependency ${importName} from ${path.relative(root, fileName)}`); } - ts.forEachChild(node, x => visit(x, fileName)); + ts.forEachChild(node, x => visit(x, fileName, text)); } function calculateDeps(from) { diff --git a/utils/generate_third_party_notice.js b/utils/generate_third_party_notice.js index 8028b4b98f..71c250cfaf 100644 --- a/utils/generate_third_party_notice.js +++ b/utils/generate_third_party_notice.js @@ -59,7 +59,7 @@ This project incorporates components from the projects listed below. The origina } } - const packages = await checkDir('node_modules/codemirror'); + const packages = await checkDir('node_modules/codemirror-shadow-1'); for (const [key, value] of Object.entries(packages)) { if (value.licenseText) allPackages[key] = value;