diff --git a/airflow-core/3rd-party-licenses/LICENSE-monaco-editor.txt b/airflow-core/3rd-party-licenses/LICENSE-monaco-editor.txt new file mode 100644 index 0000000000000..76fdc58a0d7f7 --- /dev/null +++ b/airflow-core/3rd-party-licenses/LICENSE-monaco-editor.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 - present Microsoft Corporation + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +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. diff --git a/airflow-core/LICENSE b/airflow-core/LICENSE index 4df0bdc15cc12..1d91f60c9801c 100644 --- a/airflow-core/LICENSE +++ b/airflow-core/LICENSE @@ -236,6 +236,7 @@ The text of each license is also included at 3rd-party-licenses/LICENSE-[project (MIT License) normalize.css v3.0.2 (http://necolas.github.io/normalize.css/) (MIT License) ElasticMock v1.3.2 (https://github.com/vrcmarcos/elasticmock) (MIT License) MomentJS v2.24.0 (http://momentjs.com/) + (MIT License) Monaco Editor v0.52.2 (https://github.com/microsoft/monaco-editor) (MIT License) eonasdan-bootstrap-datetimepicker v4.17.49 (https://github.com/eonasdan/bootstrap-datetimepicker/) (MIT License) Chakra UI v3.35.0 (https://github.com/chakra-ui/chakra-ui) diff --git a/airflow-core/NOTICE b/airflow-core/NOTICE index b103c97d19a99..9a1b9cddf6dc8 100644 --- a/airflow-core/NOTICE +++ b/airflow-core/NOTICE @@ -20,3 +20,11 @@ This product contains a modified portion of 'Chakra UI' developed by Segun Adeba (https://github.com/chakra-ui/chakra-ui). * Copyright 2019, Segun Adebayo + + +Monaco Editor: +----- +This product contains 'Monaco Editor' developed by Microsoft Corporation. +(https://github.com/microsoft/monaco-editor). + +* Copyright (c) 2016 - present Microsoft Corporation diff --git a/airflow-core/src/airflow/ui/package.json b/airflow-core/src/airflow/ui/package.json index 273fd20dede27..aeda5206944b6 100644 --- a/airflow-core/src/airflow/ui/package.json +++ b/airflow-core/src/airflow/ui/package.json @@ -50,6 +50,7 @@ "i18next": "^25.8.16", "i18next-browser-languagedetector": "^8.2.1", "i18next-http-backend": "^3.0.2", + "monaco-editor": "^0.52.2", "next-themes": "^0.4.6", "react": "^19.2.4", "react-chartjs-2": "^5.3.1", diff --git a/airflow-core/src/airflow/ui/pnpm-lock.yaml b/airflow-core/src/airflow/ui/pnpm-lock.yaml index face981f7ed06..a27a8cafec875 100644 --- a/airflow-core/src/airflow/ui/pnpm-lock.yaml +++ b/airflow-core/src/airflow/ui/pnpm-lock.yaml @@ -55,7 +55,7 @@ importers: version: 1.2.3 '@monaco-editor/react': specifier: ^4.7.0 - version: 4.7.0(monaco-editor@0.53.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 4.7.0(monaco-editor@0.52.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@tanstack/react-query': specifier: ^5.90.21 version: 5.90.21(react@19.2.4) @@ -113,6 +113,9 @@ importers: i18next-http-backend: specifier: '>=3.0.5' version: 4.0.0 + monaco-editor: + specifier: ^0.52.2 + version: 0.52.2 next-themes: specifier: ^0.4.6 version: 0.4.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -1057,42 +1060,36 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [glibc] '@rolldown/binding-linux-arm64-musl@1.0.0-rc.12': resolution: {integrity: sha512-V6/wZztnBqlx5hJQqNWwFdxIKN0m38p8Jas+VoSfgH54HSj9tKTt1dZvG6JRHcjh6D7TvrJPWFGaY9UBVOaWPw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [musl] '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.12': resolution: {integrity: sha512-AP3E9BpcUYliZCxa3w5Kwj9OtEVDYK6sVoUzy4vTOJsjPOgdaJZKFmN4oOlX0Wp0RPV2ETfmIra9x1xuayFB7g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] - libc: [glibc] '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.12': resolution: {integrity: sha512-nWwpvUSPkoFmZo0kQazZYOrT7J5DGOJ/+QHHzjvNlooDZED8oH82Yg67HvehPPLAg5fUff7TfWFHQS8IV1n3og==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] - libc: [glibc] '@rolldown/binding-linux-x64-gnu@1.0.0-rc.12': resolution: {integrity: sha512-RNrafz5bcwRy+O9e6P8Z/OCAJW/A+qtBczIqVYwTs14pf4iV1/+eKEjdOUta93q2TsT/FI0XYDP3TCky38LMAg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [glibc] '@rolldown/binding-linux-x64-musl@1.0.0-rc.12': resolution: {integrity: sha512-Jpw/0iwoKWx3LJ2rc1yjFrj+T7iHZn2JDg1Yny1ma0luviFS4mhAIcd1LFNxK3EYu3DHWCps0ydXQ5i/rrJ2ig==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [musl] '@rolldown/binding-openharmony-arm64@1.0.0-rc.12': resolution: {integrity: sha512-vRugONE4yMfVn0+7lUKdKvN4D5YusEiPilaoO2sgUWpCvrncvWgPMzK00ZFFJuiPgLwgFNP5eSiUlv2tfc+lpA==} @@ -1155,28 +1152,24 @@ packages: engines: {node: '>=10'} cpu: [arm64] os: [linux] - libc: [glibc] '@swc/core-linux-arm64-musl@1.15.18': resolution: {integrity: sha512-0a+Lix+FSSHBSBOA0XznCcHo5/1nA6oLLjcnocvzXeqtdjnPb+SvchItHI+lfeiuj1sClYPDvPMLSLyXFaiIKw==} engines: {node: '>=10'} cpu: [arm64] os: [linux] - libc: [musl] '@swc/core-linux-x64-gnu@1.15.18': resolution: {integrity: sha512-wG9J8vReUlpaHz4KOD/5UE1AUgirimU4UFT9oZmupUDEofxJKYb1mTA/DrMj0s78bkBiNI+7Fo2EgPuvOJfuAA==} engines: {node: '>=10'} cpu: [x64] os: [linux] - libc: [glibc] '@swc/core-linux-x64-musl@1.15.18': resolution: {integrity: sha512-4nwbVvCphKzicwNWRmvD5iBaZj8JYsRGa4xOxJmOyHlMDpsvvJ2OR2cODlvWyGFH6BYL1MfIAK3qph3hp0Az6g==} engines: {node: '>=10'} cpu: [x64] os: [linux] - libc: [musl] '@swc/core-win32-arm64-msvc@1.15.18': resolution: {integrity: sha512-zk0RYO+LjiBCat2RTMHzAWaMky0cra9loH4oRrLKLLNuL+jarxKLFDA8xTZWEkCPLjUTwlRN7d28eDLLMgtUcQ==} @@ -1425,9 +1418,6 @@ packages: '@types/statuses@2.0.6': resolution: {integrity: sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==} - '@types/trusted-types@1.0.6': - resolution: {integrity: sha512-230RC8sFeHoT6sSUlRO6a8cAnclO06eeiq1QDfiv2FGCLWFvvERWgwIQD4FWqD9A69BN7Lzee4OXwoMVnnsWDw==} - '@types/unist@2.0.11': resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} @@ -1505,6 +1495,7 @@ packages: '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + deprecated: Potential CWE-502 - Update to 1.3.1 or higher '@visx/curve@3.12.0': resolution: {integrity: sha512-Ng1mefXIzoIoAivw7dJ+ZZYYUbfuwXgZCgQynShr6ZIVw7P4q4HeQfJP3W24ON+1uCSrzoycHSXRelhR9SBPcw==} @@ -3197,28 +3188,24 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [glibc] lightningcss-linux-arm64-musl@1.32.0: resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [musl] lightningcss-linux-x64-gnu@1.32.0: resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [glibc] lightningcss-linux-x64-musl@1.32.0: resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [musl] lightningcss-win32-arm64-msvc@1.32.0: resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} @@ -3473,8 +3460,8 @@ packages: mlly@1.8.0: resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==} - monaco-editor@0.53.0: - resolution: {integrity: sha512-0WNThgC6CMWNXXBxTbaYYcunj08iB5rnx4/G56UOPeL9UVIUGGHA1GR0EWIh9Ebabj7NpCRawQ5b0hfN1jQmYQ==} + monaco-editor@0.52.2: + resolution: {integrity: sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ==} ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -5382,10 +5369,10 @@ snapshots: dependencies: state-local: 1.0.7 - '@monaco-editor/react@4.7.0(monaco-editor@0.53.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@monaco-editor/react@4.7.0(monaco-editor@0.52.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@monaco-editor/loader': 1.5.0 - monaco-editor: 0.53.0 + monaco-editor: 0.52.2 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) @@ -5779,8 +5766,6 @@ snapshots: '@types/statuses@2.0.6': {} - '@types/trusted-types@1.0.6': {} - '@types/unist@2.0.11': {} '@types/unist@3.0.3': {} @@ -8588,9 +8573,7 @@ snapshots: pkg-types: 1.3.1 ufo: 1.6.1 - monaco-editor@0.53.0: - dependencies: - '@types/trusted-types': 1.0.6 + monaco-editor@0.52.2: {} ms@2.1.3: {} diff --git a/airflow-core/src/airflow/ui/src/components/JsonEditor.tsx b/airflow-core/src/airflow/ui/src/components/JsonEditor.tsx index 806b73b7d2625..060df05ebcecb 100644 --- a/airflow-core/src/airflow/ui/src/components/JsonEditor.tsx +++ b/airflow-core/src/airflow/ui/src/components/JsonEditor.tsx @@ -16,9 +16,9 @@ * specific language governing permissions and limitations * under the License. */ -import Editor, { type EditorProps } from "@monaco-editor/react"; import { useRef } from "react"; +import Editor, { type EditorProps } from "src/components/MonacoEditor"; import { useMonacoTheme } from "src/context/colorMode"; type JsonEditorProps = { diff --git a/airflow-core/src/airflow/ui/src/components/MonacoEditor/configureMonaco.ts b/airflow-core/src/airflow/ui/src/components/MonacoEditor/configureMonaco.ts new file mode 100644 index 0000000000000..629b0cc46b69c --- /dev/null +++ b/airflow-core/src/airflow/ui/src/components/MonacoEditor/configureMonaco.ts @@ -0,0 +1,71 @@ +/*! + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { loader } from "@monaco-editor/react"; + +type MonacoEnvironment = { + readonly getWorker: (_moduleId: string, label: string) => Worker; +}; + +let configurationPromise: Promise | undefined; + +const loadMonacoModules = async () => { + const monacoApi = import("monaco-editor/esm/vs/editor/editor.api"); + + const workerConstructors = Promise.all([ + import("monaco-editor/esm/vs/editor/editor.worker?worker").then((module) => module.default), + import("monaco-editor/esm/vs/language/json/json.worker?worker").then((module) => module.default), + ]); + + const languageContributions = Promise.all([ + import("monaco-editor/esm/vs/basic-languages/python/python.contribution"), + import("monaco-editor/esm/vs/language/json/monaco.contribution"), + ]); + + const [monaco, [editorWorker, jsonWorker]] = await Promise.all([ + monacoApi, + workerConstructors, + languageContributions, + ]); + + return { editorWorker, jsonWorker, monaco }; +}; + +export const configureMonaco = () => { + if (configurationPromise !== undefined) { + return configurationPromise; + } + + configurationPromise = loadMonacoModules() + .then(({ editorWorker, jsonWorker, monaco }) => { + Reflect.set(globalThis, "MonacoEnvironment", { + getWorker: (_moduleId: string, label: string) => + label === "json" ? new jsonWorker() : new editorWorker(), + } satisfies MonacoEnvironment); + + loader.config({ monaco }); + }) + .catch((error: unknown) => { + configurationPromise = undefined; + // eslint-disable-next-line no-console + console.error("Failed to configure Monaco editor", error); + throw error; + }); + + return configurationPromise; +}; diff --git a/airflow-core/src/airflow/ui/src/components/MonacoEditor/index.tsx b/airflow-core/src/airflow/ui/src/components/MonacoEditor/index.tsx new file mode 100644 index 0000000000000..949146d652478 --- /dev/null +++ b/airflow-core/src/airflow/ui/src/components/MonacoEditor/index.tsx @@ -0,0 +1,51 @@ +/*! + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import EditorComponent, { + DiffEditor as DiffEditorComponent, + type DiffEditorProps, + type EditorProps, +} from "@monaco-editor/react"; + +import { useMonacoReady } from "./useMonacoReady"; + +export const MonacoEditor = (props: EditorProps) => { + const isMonacoReady = useMonacoReady(); + + if (!isMonacoReady) { + return null; + } + + return ; +}; + +export const MonacoDiffEditor = (props: DiffEditorProps) => { + const isMonacoReady = useMonacoReady(); + + if (!isMonacoReady) { + return null; + } + + return ; +}; + +export const DiffEditor = MonacoDiffEditor; + +export default MonacoEditor; + +export type { DiffEditorProps, EditorProps, Monaco, OnMount } from "@monaco-editor/react"; diff --git a/airflow-core/src/airflow/ui/src/components/MonacoEditor/useMonacoReady.ts b/airflow-core/src/airflow/ui/src/components/MonacoEditor/useMonacoReady.ts new file mode 100644 index 0000000000000..b8a6821a0c7b2 --- /dev/null +++ b/airflow-core/src/airflow/ui/src/components/MonacoEditor/useMonacoReady.ts @@ -0,0 +1,43 @@ +/*! + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { useEffect, useState } from "react"; + +import { configureMonaco } from "./configureMonaco"; + +export const useMonacoReady = () => { + const [isReady, setIsReady] = useState(false); + + useEffect(() => { + let isMounted = true; + + void configureMonaco() + .then(() => { + if (isMounted) { + setIsReady(true); + } + }) + .catch(() => undefined); + + return () => { + isMounted = false; + }; + }, []); + + return isReady; +}; diff --git a/airflow-core/src/airflow/ui/src/components/RenderedJsonField.tsx b/airflow-core/src/airflow/ui/src/components/RenderedJsonField.tsx index 87308e6fdd765..ff629eaa1fb5c 100644 --- a/airflow-core/src/airflow/ui/src/components/RenderedJsonField.tsx +++ b/airflow-core/src/airflow/ui/src/components/RenderedJsonField.tsx @@ -17,9 +17,9 @@ * under the License. */ import { Flex, type FlexProps } from "@chakra-ui/react"; -import Editor, { type OnMount } from "@monaco-editor/react"; import { useCallback, useState } from "react"; +import Editor, { type OnMount } from "src/components/MonacoEditor"; import { ClipboardRoot, ClipboardIconButton } from "src/components/ui"; import { useMonacoTheme } from "src/context/colorMode"; diff --git a/airflow-core/src/airflow/ui/src/pages/Dag/Code/Code.tsx b/airflow-core/src/airflow/ui/src/pages/Dag/Code/Code.tsx index a3dd224f890d9..7082410d553b6 100644 --- a/airflow-core/src/airflow/ui/src/pages/Dag/Code/Code.tsx +++ b/airflow-core/src/airflow/ui/src/pages/Dag/Code/Code.tsx @@ -19,7 +19,6 @@ /* eslint-disable max-lines */ import { Box, Button, Heading, HStack, Link, VStack } from "@chakra-ui/react"; -import Editor, { type EditorProps } from "@monaco-editor/react"; import { useState } from "react"; import { useHotkeys } from "react-hotkeys-hook"; import { useTranslation } from "react-i18next"; @@ -35,6 +34,7 @@ import type { ApiError } from "openapi/requests/core/ApiError"; import type { DAGSourceResponse } from "openapi/requests/types.gen"; import { DagVersionSelect } from "src/components/DagVersionSelect"; import { ErrorAlert } from "src/components/ErrorAlert"; +import Editor, { type EditorProps } from "src/components/MonacoEditor"; import Time from "src/components/Time"; import { ClipboardRoot, ClipboardButton, Tooltip } from "src/components/ui"; import { ProgressBar } from "src/components/ui"; diff --git a/airflow-core/src/airflow/ui/src/pages/Dag/Code/CodeDiffViewer.tsx b/airflow-core/src/airflow/ui/src/pages/Dag/Code/CodeDiffViewer.tsx index 08acae1b42a76..8f6b89bc60832 100644 --- a/airflow-core/src/airflow/ui/src/pages/Dag/Code/CodeDiffViewer.tsx +++ b/airflow-core/src/airflow/ui/src/pages/Dag/Code/CodeDiffViewer.tsx @@ -17,8 +17,8 @@ * under the License. */ import { Box } from "@chakra-ui/react"; -import { DiffEditor, type DiffEditorProps } from "@monaco-editor/react"; +import { DiffEditor, type DiffEditorProps } from "src/components/MonacoEditor"; import { useMonacoTheme } from "src/context/colorMode"; type CodeDiffViewerProps = { diff --git a/airflow-core/src/airflow/ui/tests/e2e/pages/DagCodePage.ts b/airflow-core/src/airflow/ui/tests/e2e/pages/DagCodePage.ts index fbb2f27ab59b8..ae254aad5c108 100644 --- a/airflow-core/src/airflow/ui/tests/e2e/pages/DagCodePage.ts +++ b/airflow-core/src/airflow/ui/tests/e2e/pages/DagCodePage.ts @@ -29,7 +29,7 @@ export class DagCodePage extends BasePage { public constructor(page: Page) { super(page); this.editorContainer = page.locator('[role="code"]'); - this.lineNumbers = page.locator(".monaco-editor .line-numbers"); + this.lineNumbers = page.locator(".monaco-editor .margin-view-overlays .line-numbers"); this.editorScrollable = page.locator(".monaco-scrollable-element"); this.syntaxTokens = page.locator(".monaco-editor .view-line span span"); this.viewLines = page.locator(".monaco-editor .view-line");