Skip to content

Commit 9dadae4

Browse files
authored
match upstream default setting for editor.editContext (enabled) (#11006)
### Summary - addresses #9327 - approach: update default `editor.editContext` setting to match upstream, to fix output copying issue. If this setting is disabled in VS Code, the same issue occurs there as well. - small code change, but has widespread implications for e2e tests as the target locators and corresponding methods now need to be compatible with the `.native-edit-context` div, instead of a `textarea` <details><summary>Range of tests failing (hoping to fix these in this PR)</summary> Essentially anything that is backed with a monaco editor involving copy or paste, so Console, Notebook, Connections, Output, Assistant chat, etc. <img width="303" height="264" alt="image" src="https://github.com/user-attachments/assets/a20e97f4-a3a9-4882-a7ba-2a256a9dda65" /> <img width="306" height="423" alt="image" src="https://github.com/user-attachments/assets/bbbf0a43-096b-4150-8061-b35dce2d3b61" /> <img width="343" height="423" alt="image" src="https://github.com/user-attachments/assets/a126b8e6-0abc-40ae-b9c8-d8046300d3de" /> </details> ### Release Notes #### Bug Fixes - Fix issue preventing text from being copied from Output pane (#9327) ### QA Notes @:output - added a test to copy ~1000 characters from the output pane and paste to an editor - the test is skipped because the assertion is failing in CI, though passing locally for me. Seems like an issue with newlines/normalization getting in the way of the string match This setting was originally disabled by default for this reason: > // Disable the edit context which is normally enabled. > // It causes test failures due to changes in the DOM. This was when the setting was still experimental, and the setting was called `editor.experimentalEditContextEnabled` during that time This setting was migrated to be non-experimental (with the current setting name `editor.editContext`) in 1.102: microsoft/vscode@312284e Now that we are enabling `editor.editContext` by default, locators using `textarea` have been updated to `.native-edit-context`, with the exception of `test/e2e/pages/search.ts`, which seems to still actually use a textarea. Because of this change from `textarea` --> `.native-edit-context` div, we can no longer call `.fill()` on the locators, because `Error: locator.fill: Error: Element is not an <input>, <textarea> or [contenteditable] element`, so `.fill()` has been replaced with `.pressSequentially()`.
1 parent 278f002 commit 9dadae4

File tree

11 files changed

+142
-32
lines changed

11 files changed

+142
-32
lines changed

src/vs/editor/common/config/editorOptions.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6284,12 +6284,7 @@ export const EditorOptions = {
62846284
emptySelectionClipboard: register(new EditorEmptySelectionClipboard()),
62856285
dropIntoEditor: register(new EditorDropIntoEditor()),
62866286
editContext: register(new EditorBooleanOption(
6287-
// --- Start Positron ---
6288-
// Disable the edit context which is normally enabled.
6289-
// It causes test failures due to changes in the DOM.
6290-
// https://positpbc.slack.com/archives/C04FPQK3H9C/p1740750244836859
6291-
EditorOption.editContext, 'editContext', false,
6292-
// --- End Positron ---
6287+
EditorOption.editContext, 'editContext', true,
62936288
{
62946289
description: nls.localize('editContext', "Sets whether the EditContext API should be used instead of the text area to power input in the editor."),
62956290
included: platform.isChrome || platform.isEdge || platform.isNative

test/e2e/pages/console.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -299,11 +299,11 @@ export class Console {
299299
text: string,
300300
maxRetries = 3
301301
): Promise<void> {
302-
const textarea = locator.locator('textarea');
302+
const editContext = locator.locator('.native-edit-context');
303303

304304
for (let attempt = 1; attempt <= maxRetries; attempt++) {
305305
// Attempt paste
306-
await textarea.evaluate(async (element, evalText) => {
306+
await editContext.evaluate(async (element, evalText) => {
307307
const clipboardData = new DataTransfer();
308308
clipboardData.setData('text/plain', evalText);
309309
const clipboardEvent = new ClipboardEvent('paste', {

test/e2e/pages/editor.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,12 +133,12 @@ export class Editor {
133133
async waitForEditorFocus(filename: string, lineNumber: number, selectorPrefix = ''): Promise<void> {
134134
const editor = [selectorPrefix || '', EDITOR(filename)].join(' ');
135135
const line = `${editor} .view-lines > .view-line:nth-child(${lineNumber})`;
136-
const textarea = `${editor} textarea`;
136+
const editContext = `${editor} .native-edit-context`;
137137

138138
await this.code.driver.page.locator(line).click();
139139

140140
await expect(async () => {
141-
await expect(this.code.driver.page.locator(textarea)).toBeFocused();
141+
await expect(this.code.driver.page.locator(editContext)).toBeFocused();
142142
}).toPass();
143143
}
144144

test/e2e/pages/editors.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ export class Editors {
8383
}
8484

8585
async waitForActiveEditor(fileName: string): Promise<any> {
86-
const selector = `.editor-instance .monaco-editor[data-uri$="${fileName}"] textarea`;
86+
const selector = `.editor-instance .monaco-editor[data-uri$="${fileName}"] .native-edit-context`;
8787
await expect(this.code.driver.page.locator(selector)).toBeFocused();
8888
}
8989

test/e2e/pages/extensions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export class Extensions {
1313

1414
async searchForExtension(id: string): Promise<void> {
1515
await this.quickaccess.runCommand('Extensions: Focus on Extensions View', { exactLabelMatch: true });
16-
await this.code.driver.page.locator('div.extensions-viewlet[id="workbench.view.extensions"] .monaco-editor textarea').fill(`@id:${id}`);
16+
await this.code.driver.page.locator('div.extensions-viewlet[id="workbench.view.extensions"] .monaco-editor .native-edit-context').pressSequentially(`@id:${id}`);
1717
await expect(this.code.driver.page.locator(`div.part.sidebar div.composite.title h2`)).toHaveText('Extensions: Marketplace');
1818

1919
let retrials = 1;

test/e2e/pages/notebooks.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -194,12 +194,10 @@ export class Notebooks {
194194

195195
await this.code.driver.page.locator(editor).isVisible();
196196

197-
const textarea = `${editor} textarea`;
198-
await expect(this.code.driver.page.locator(textarea)).toBeFocused();
197+
const editContext = `${editor} .native-edit-context`;
198+
await expect(this.code.driver.page.locator(editContext)).toBeFocused();
199199

200-
delay
201-
? await this.code.driver.page.locator(textarea).pressSequentially(text, { delay })
202-
: await this.code.driver.page.locator(textarea).fill(text);
200+
await this.code.driver.page.locator(editContext).pressSequentially(text, { delay });
203201
});
204202
}
205203

test/e2e/pages/notebooksPositron.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export class PositronNotebooks extends Notebooks {
2727
private positronNotebook = this.code.driver.page.locator('.positron-notebook').first();
2828
private newCellButton = this.code.driver.page.getByLabel(/new code cell/i);
2929
private spinner = this.code.driver.page.getByLabel(/cell is executing/i);
30-
editorAtIndex = (index: number) => this.cell.nth(index).locator('.positron-cell-editor-monaco-widget textarea');
30+
editorAtIndex = (index: number) => this.cell.nth(index).locator('.positron-cell-editor-monaco-widget .native-edit-context');
3131
cell = this.code.driver.page.locator('[data-testid="notebook-cell"]');
3232
codeCell = this.code.driver.page.locator('[data-testid="notebook-cell"][aria-label="Code cell"]');
3333
markdownCell = this.code.driver.page.locator(`[data-testid="notebook-cell"][aria-label="${MARKDOWN_ARIA_LABEL}"]`);
@@ -427,11 +427,7 @@ export class PositronNotebooks extends Notebooks {
427427
const editor = this.editorAtIndex(cellIndex);
428428
await editor.focus();
429429

430-
if (delay) {
431-
await editor.pressSequentially(code, { delay });
432-
} else {
433-
await editor.fill(code);
434-
}
430+
await editor.pressSequentially(code, { delay });
435431

436432
if (run) {
437433
await this.runCellButtonAtIndex(cellIndex).click();

test/e2e/pages/output.ts

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
/*---------------------------------------------------------------------------------------------
2-
* Copyright (C) 2024 Posit Software, PBC. All rights reserved.
2+
* Copyright (C) 2024-2025 Posit Software, PBC. All rights reserved.
33
* Licensed under the Elastic License 2.0. See LICENSE.txt for license information.
44
*--------------------------------------------------------------------------------------------*/
55

66

7+
import * as os from 'os';
78
import { Code } from '../infra/code';
89
import { QuickAccess } from './quickaccess';
910
import { QuickInput } from './quickInput';
1011

1112
const OUTPUT_LINE = '.view-line';
13+
const OUTPUT_PANE = 'div[id="workbench.panel.output"]';
1214

1315
/*
1416
* Reuseable Positron output functionality for tests to leverage.
@@ -32,7 +34,78 @@ export class Output {
3234
}
3335

3436
async waitForOutContaining(fragment: string) {
35-
const outputLine = this.code.driver.page.locator(OUTPUT_LINE);
37+
const outputPane = this.code.driver.page.locator(OUTPUT_PANE);
38+
const outputLine = outputPane.locator(OUTPUT_LINE);
3639
await outputLine.getByText(fragment).first().isVisible();
3740
}
41+
42+
/**
43+
* Scroll to the top of the output pane
44+
*/
45+
async scrollToTop(): Promise<void> {
46+
// First, ensure the output pane is focused
47+
await this.quickaccess.runCommand('workbench.panel.output.focus');
48+
49+
// Use platform-specific keyboard shortcuts to scroll to top
50+
const platform = os.platform();
51+
if (platform === 'darwin') {
52+
// On macOS, use Cmd+ArrowUp
53+
await this.code.driver.page.keyboard.press('Meta+ArrowUp');
54+
} else {
55+
// On Windows/Linux, use Ctrl+Home
56+
await this.code.driver.page.keyboard.press('Control+Home');
57+
}
58+
}
59+
60+
/**
61+
* Copy selected text from the output pane and return it
62+
*/
63+
async copySelectedText(): Promise<string> {
64+
const isMac = os.platform() === 'darwin';
65+
const modifier = isMac ? 'Meta' : 'Control';
66+
67+
await this.code.driver.page.keyboard.press(`${modifier}+C`);
68+
69+
// Wait a bit for the copy operation to complete
70+
await this.code.driver.page.waitForTimeout(100);
71+
72+
// Grant permissions to read from clipboard
73+
await this.code.driver.context.grantPermissions(['clipboard-read']);
74+
75+
// Read the clipboard content
76+
const clipboardText = await this.code.driver.page.evaluate(async () => {
77+
try {
78+
return await navigator.clipboard.readText();
79+
} catch (error) {
80+
console.error('Failed to read clipboard text:', error);
81+
return '';
82+
}
83+
});
84+
85+
return clipboardText;
86+
}
87+
88+
/**
89+
* Select the first N lines of output text
90+
*/
91+
async selectFirstNLines(lineCount: number): Promise<void> {
92+
const outputPane = this.code.driver.page.locator(OUTPUT_PANE);
93+
const outputLines = outputPane.locator('.view-line');
94+
const totalLines = await outputLines.count();
95+
96+
if (totalLines === 0) {
97+
throw new Error('No output lines found in the output pane');
98+
}
99+
100+
// Calculate how many lines to select (or all lines if less than N)
101+
const linesToSelect = Math.min(lineCount, totalLines);
102+
const endLineIndex = linesToSelect - 1;
103+
104+
// Click on the first line and then shift+click on the last line of selection
105+
const startLine = outputLines.nth(0);
106+
const endLine = outputLines.nth(endLineIndex);
107+
108+
await startLine.click();
109+
await endLine.click({ modifiers: ['Shift'] });
110+
}
38111
}

test/e2e/pages/positronAssistant.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ const COPY_BUTTON = 'a.action-label.codicon.codicon-copy[role="button"][aria-lab
3232
const INSERT_NEW_FILE_BUTTON = 'a.action-label.codicon.codicon-new-file[role="button"][aria-label="Insert into New File"]';
3333
const OAUTH_RADIO = '.language-model-authentication-method-container input#oauth[type="radio"]';
3434
const APIKEY_RADIO = '.language-model-authentication-method-container input#apiKey[type="radio"]';
35-
const CHAT_INPUT = '.chat-editor-container .interactive-input-editor textarea.inputarea';
35+
const CHAT_INPUT = '.chat-editor-container .interactive-input-editor .native-edit-context';
3636
const SEND_MESSAGE_BUTTON = '.actions-container .action-label.codicon-send[aria-label^="Send"]';
3737
const NEW_CHAT_BUTTON = '.composite.title .actions-container[aria-label="Chat actions"] .action-item .action-label.codicon-plus[aria-label^="New Chat"]';
3838
const INLINE_CHAT_TOOLBAR = '.interactive-input-part.compact .chat-input-toolbars';
@@ -199,7 +199,7 @@ export class Assistant {
199199
async enterChatMessage(message: string, waitForResponse: boolean = true) {
200200
const chatInput = this.code.driver.page.locator(CHAT_INPUT);
201201
await chatInput.waitFor({ state: 'visible' });
202-
await chatInput.fill(message);
202+
await chatInput.pressSequentially(message);
203203
await this.code.driver.page.locator(SEND_MESSAGE_BUTTON).click();
204204
// It can take a moment for the loading locator to become visible.
205205
await this.code.driver.page.locator('.chat-most-recent-response.chat-response-loading').waitFor({ state: 'visible' });

test/e2e/pages/scm.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { Layouts } from './layouts';
1212
*/
1313

1414
const VIEWLET = 'div[id="workbench.view.scm"]';
15-
const SCM_INPUT_TEXTAREA = `${VIEWLET} .scm-editor textarea`;
15+
const SCM_INPUT_EDIT_CONTEXT = `${VIEWLET} .scm-editor .native-edit-context`;
1616
const SCM_RESOURCE_CLICK = (name: string) => `${VIEWLET} .monaco-list-row .resource .monaco-icon-label[aria-label*="${name}"] .label-name`;
1717
const SCM_RESOURCE_ACTION_CLICK = (name: string, actionName: string) => `.monaco-list-row .resource .monaco-icon-label[aria-label*="${name}"] .actions .action-label[aria-label="${actionName}"]`;
1818
const COMMIT_COMMAND = `div[id="workbench.parts.sidebar"] .actions-container a.action-label[aria-label="Commit"]`;
@@ -24,7 +24,7 @@ export class SCM {
2424

2525
async openSCMViewlet(): Promise<any> {
2626
await this.code.driver.page.keyboard.press('Control+Shift+G');
27-
await expect(this.code.driver.page.locator(SCM_INPUT_TEXTAREA)).toBeVisible();
27+
await expect(this.code.driver.page.locator(SCM_INPUT_EDIT_CONTEXT)).toBeVisible();
2828
}
2929

3030
async waitForChange(name: string, type: 'Staged' | 'Modified'): Promise<void> {
@@ -57,9 +57,9 @@ export class SCM {
5757

5858
async commit(message: string): Promise<void> {
5959
await this.code.driver.page.keyboard.press('Control+Shift+G'); // need to switch to scm view as it may have reset
60-
await this.code.driver.page.locator(SCM_INPUT_TEXTAREA).click({ force: true });
61-
await expect(this.code.driver.page.locator(SCM_INPUT_TEXTAREA)).toBeFocused();
62-
await this.code.driver.page.locator(SCM_INPUT_TEXTAREA).fill(message);
60+
await this.code.driver.page.locator(SCM_INPUT_EDIT_CONTEXT).click({ force: true });
61+
await expect(this.code.driver.page.locator(SCM_INPUT_EDIT_CONTEXT)).toBeFocused();
62+
await this.code.driver.page.locator(SCM_INPUT_EDIT_CONTEXT).pressSequentially(message);
6363
await this.code.driver.page.locator(COMMIT_COMMAND).click();
6464
}
6565

0 commit comments

Comments
 (0)