From 67b3e78ceb932dbbfbd8787ffcd4558ed272fe85 Mon Sep 17 00:00:00 2001 From: Philip Langer Date: Fri, 25 Mar 2022 10:15:56 +0100 Subject: [PATCH] [playwright] Add option preference support and reactive tests Due to the Monaco uplift, we had to temporarily skip a test, because it affected the default preference regarding auto-save. To re-enable the test, we now explicitly set the auto save preference to `off` before the test. This way the preference value is ensured to be consistent. In order to do so, we add support for option preferences See also https://github.com/eclipse-theia/theia/pull/10736 Fixes https://github.com/eclipse-theia/theia/issues/10891 Change-Id: I6ab22a71848e174313a30f9121cb4b3dec363f12 --- .../src/tests/theia-preference-view.test.ts | 51 ++++-- .../src/tests/theia-text-editor.test.ts | 8 +- .../playwright/src/theia-preference-view.ts | 150 +++++++++--------- 3 files changed, 123 insertions(+), 86 deletions(-) diff --git a/examples/playwright/src/tests/theia-preference-view.test.ts b/examples/playwright/src/tests/theia-preference-view.test.ts index 8b60dd3eca399..9d6059c88e2f7 100644 --- a/examples/playwright/src/tests/theia-preference-view.test.ts +++ b/examples/playwright/src/tests/theia-preference-view.test.ts @@ -38,14 +38,14 @@ test.describe('Preference View', () => { const preferences = await app.openPreferences(TheiaPreferenceView); const preferenceId = PreferenceIds.DiffEditor.MaxComputationTime; - await preferences.resetStringPreferenceById(preferenceId); + await preferences.resetPreferenceById(preferenceId); expect(await preferences.getStringPreferenceById(preferenceId)).toBe(DefaultPreferences.DiffEditor.MaxComputationTime); await preferences.setStringPreferenceById(preferenceId, '8000'); await preferences.waitForModified(preferenceId); expect(await preferences.getStringPreferenceById(preferenceId)).toBe('8000'); - await preferences.resetStringPreferenceById(preferenceId); + await preferences.resetPreferenceById(preferenceId); expect(await preferences.getStringPreferenceById(preferenceId)).toBe(DefaultPreferences.DiffEditor.MaxComputationTime); }); @@ -53,29 +53,48 @@ test.describe('Preference View', () => { const preferences = await app.openPreferences(TheiaPreferenceView); const preferenceId = PreferenceIds.Explorer.AutoReveal; - await preferences.resetBooleanPreferenceById(preferenceId); + await preferences.resetPreferenceById(preferenceId); expect(await preferences.getBooleanPreferenceById(preferenceId)).toBe(DefaultPreferences.Explorer.AutoReveal.Enabled); await preferences.setBooleanPreferenceById(preferenceId, false); await preferences.waitForModified(preferenceId); expect(await preferences.getBooleanPreferenceById(preferenceId)).toBe(false); - await preferences.resetBooleanPreferenceById(preferenceId); + await preferences.resetPreferenceById(preferenceId); expect(await preferences.getBooleanPreferenceById(preferenceId)).toBe(DefaultPreferences.Explorer.AutoReveal.Enabled); }); + test('should be able to read, set, and reset Options preferences', async () => { + const preferences = await app.openPreferences(TheiaPreferenceView); + const preferenceId = PreferenceIds.Editor.RenderWhitespace; + + await preferences.resetPreferenceById(preferenceId); + expect(await preferences.getOptionsPreferenceById(preferenceId)).toBe(DefaultPreferences.Editor.RenderWhitespace.Selection); + + await preferences.setOptionsPreferenceById(preferenceId, DefaultPreferences.Editor.RenderWhitespace.Boundary); + await preferences.waitForModified(preferenceId); + expect(await preferences.getOptionsPreferenceById(preferenceId)).toBe(DefaultPreferences.Editor.RenderWhitespace.Boundary); + + await preferences.resetPreferenceById(preferenceId); + expect(await preferences.getOptionsPreferenceById(preferenceId)).toBe(DefaultPreferences.Editor.RenderWhitespace.Selection); + }); + test('should throw an error if we try to read, set, or reset a non-existing preference', async () => { const preferences = await app.openPreferences(TheiaPreferenceView); preferences.customTimeout = 500; try { - await expect(preferences.getBooleanPreferenceById('no.a.real.preference')).rejects.toThrowError(); - await expect(preferences.setBooleanPreferenceById('no.a.real.preference', true)).rejects.toThrowError(); - await expect(preferences.resetBooleanPreferenceById('no.a.real.preference')).rejects.toThrowError(); + await expect(preferences.getBooleanPreferenceById('not.a.real.preference')).rejects.toThrowError(); + await expect(preferences.setBooleanPreferenceById('not.a.real.preference', true)).rejects.toThrowError(); + await expect(preferences.resetPreferenceById('not.a.real.preference')).rejects.toThrowError(); - await expect(preferences.getStringPreferenceById('no.a.real.preference')).rejects.toThrowError(); - await expect(preferences.setStringPreferenceById('no.a.real.preference', 'a')).rejects.toThrowError(); - await expect(preferences.resetStringPreferenceById('no.a.real.preference')).rejects.toThrowError(); + await expect(preferences.getStringPreferenceById('not.a.real.preference')).rejects.toThrowError(); + await expect(preferences.setStringPreferenceById('not.a.real.preference', 'a')).rejects.toThrowError(); + await expect(preferences.resetPreferenceById('not.a.real.preference')).rejects.toThrowError(); + + await expect(preferences.getOptionsPreferenceById('not.a.real.preference')).rejects.toThrowError(); + await expect(preferences.setOptionsPreferenceById('not.a.real.preference', 'a')).rejects.toThrowError(); + await expect(preferences.resetPreferenceById('not.a.real.preference')).rejects.toThrowError(); } finally { preferences.customTimeout = undefined; } @@ -86,8 +105,14 @@ test.describe('Preference View', () => { const stringPreference = PreferenceIds.DiffEditor.MaxComputationTime; const booleanPreference = PreferenceIds.Explorer.AutoReveal; - await expect(preferences.getBooleanPreferenceById(stringPreference)).rejects.toThrowError(); - await expect(preferences.setBooleanPreferenceById(stringPreference, true)).rejects.toThrowError(); - await expect(preferences.setStringPreferenceById(booleanPreference, 'true')).rejects.toThrowError(); + preferences.customTimeout = 500; + try { + await expect(preferences.getBooleanPreferenceById(stringPreference)).rejects.toThrowError(); + await expect(preferences.setBooleanPreferenceById(stringPreference, true)).rejects.toThrowError(); + await expect(preferences.setStringPreferenceById(booleanPreference, 'true')).rejects.toThrowError(); + await expect(preferences.setOptionsPreferenceById(booleanPreference, 'true')).rejects.toThrowError(); + } finally { + preferences.customTimeout = undefined; + } }); }); diff --git a/examples/playwright/src/tests/theia-text-editor.test.ts b/examples/playwright/src/tests/theia-text-editor.test.ts index d2f7b3a5dde33..31bb6bab02376 100644 --- a/examples/playwright/src/tests/theia-text-editor.test.ts +++ b/examples/playwright/src/tests/theia-text-editor.test.ts @@ -15,6 +15,7 @@ // ***************************************************************************** import { expect } from '@playwright/test'; +import { DefaultPreferences, PreferenceIds, TheiaPreferenceView } from '../theia-preference-view'; import { TheiaApp } from '../theia-app'; import { TheiaTextEditor } from '../theia-text-editor'; import { TheiaWorkspace } from '../theia-workspace'; @@ -27,6 +28,11 @@ test.describe('Theia Text Editor', () => { test.beforeAll(async () => { const ws = new TheiaWorkspace(['src/tests/resources/sample-files1']); app = await TheiaApp.load(page, ws); + + // set auto-save preference to off + const preferenceView = await app.openPreferences(TheiaPreferenceView); + await preferenceView.setOptionsPreferenceById(PreferenceIds.Editor.AutoSave, DefaultPreferences.Editor.AutoSave.Off); + await preferenceView.close(); }); test('should be visible and active after opening "sample.txt"', async () => { @@ -164,7 +170,7 @@ test.describe('Theia Text Editor', () => { await sampleTextEditor.saveAndClose(); }); - test.skip('should close without saving', async () => { + test('should close without saving', async () => { const sampleTextEditor = await app.openEditor('sample.txt', TheiaTextEditor); await sampleTextEditor.replaceLineWithLineNumber('change again', 1); expect(await sampleTextEditor.isDirty()).toBe(true); diff --git a/examples/playwright/src/theia-preference-view.ts b/examples/playwright/src/theia-preference-view.ts index 5590add007977..179ae0ba8206e 100644 --- a/examples/playwright/src/theia-preference-view.ts +++ b/examples/playwright/src/theia-preference-view.ts @@ -24,6 +24,10 @@ const TheiaSettingsViewData = { }; export const PreferenceIds = { + Editor: { + AutoSave: 'files.autoSave', + RenderWhitespace: 'editor.renderWhitespace' + }, Explorer: { AutoReveal: 'explorer.autoReveal' }, @@ -33,6 +37,21 @@ export const PreferenceIds = { }; export const DefaultPreferences = { + Editor: { + AutoSave: { + Off: 'off', + AfterDelay: 'afterDelay', + OnFocusChange: 'onFocusChange', + OnWindowChange: 'onWindowChange' + }, + RenderWhitespace: { + None: 'none', + Boundary: 'boundary', + Selection: 'selection', + Trailing: 'trailing', + All: 'all' + } + }, Explorer: { AutoReveal: { Enabled: true @@ -50,6 +69,10 @@ export enum TheiaPreferenceScope { export class TheiaPreferenceView extends TheiaView { public customTimeout?: number; + protected modificationIndicator = '.theia-mod-item-modified'; + protected optionSelectLabel = '.theia-select-component-label'; + protected optionSelectDropdown = '.theia-select-component-dropdown'; + protected optionSelectDropdownValue = '.theia-select-component-option-value'; constructor(app: TheiaApp) { super(TheiaSettingsViewData, app); @@ -72,14 +95,19 @@ export class TheiaPreferenceView extends TheiaView { await scopeTab.click(); } + async getBooleanPreferenceByPath(sectionTitle: string, name: string): Promise { + const preferenceId = await this.findPreferenceId(sectionTitle, name); + return this.getBooleanPreferenceById(preferenceId); + } + async getBooleanPreferenceById(preferenceId: string): Promise { const element = await this.findPreferenceEditorById(preferenceId); return element.isChecked(); } - async getBooleanPreferenceByPath(sectionTitle: string, name: string): Promise { + async setBooleanPreferenceByPath(sectionTitle: string, name: string, value: boolean): Promise { const preferenceId = await this.findPreferenceId(sectionTitle, name); - return this.getBooleanPreferenceById(preferenceId); + return this.setBooleanPreferenceById(preferenceId, value); } async setBooleanPreferenceById(preferenceId: string, value: boolean): Promise { @@ -87,9 +115,9 @@ export class TheiaPreferenceView extends TheiaView { return value ? element.check() : element.uncheck(); } - async setBooleanPreferenceByPath(sectionTitle: string, name: string, value: boolean): Promise { + async getStringPreferenceByPath(sectionTitle: string, name: string): Promise { const preferenceId = await this.findPreferenceId(sectionTitle, name); - return this.setBooleanPreferenceById(preferenceId, value); + return this.getStringPreferenceById(preferenceId); } async getStringPreferenceById(preferenceId: string): Promise { @@ -97,9 +125,9 @@ export class TheiaPreferenceView extends TheiaView { return element.evaluate(e => (e as HTMLInputElement).value); } - async getStringPreferenceByPath(sectionTitle: string, name: string): Promise { + async setStringPreferenceByPath(sectionTitle: string, name: string, value: string): Promise { const preferenceId = await this.findPreferenceId(sectionTitle, name); - return this.getStringPreferenceById(preferenceId); + return this.setStringPreferenceById(preferenceId, value); } async setStringPreferenceById(preferenceId: string, value: string): Promise { @@ -107,63 +135,39 @@ export class TheiaPreferenceView extends TheiaView { return element.fill(value); } - async setStringPreferenceByPath(sectionTitle: string, name: string, value: string): Promise { + async getOptionsPreferenceByPath(sectionTitle: string, name: string): Promise { const preferenceId = await this.findPreferenceId(sectionTitle, name); - return this.setStringPreferenceById(preferenceId, value); + return this.getOptionsPreferenceById(preferenceId); } - async waitForModified(preferenceId: string): Promise { - await this.activate(); - const viewElement = await this.viewElement(); - await viewElement?.waitForSelector(`${this.getPreferenceGutterSelector(preferenceId)}.theia-mod-item-modified`, { timeout: this.customTimeout }); + async getOptionsPreferenceById(preferenceId: string): Promise { + const element = await this.findPreferenceEditorById(preferenceId, this.optionSelectLabel); + return element.evaluate(e => e.textContent ?? ''); } - async resetStringPreferenceById(preferenceId: string): Promise { - const resetPreferenceButton = await this.findPreferenceResetButton(preferenceId); - if (!resetPreferenceButton) { - // preference not modified - return; - } - const previousValue = await this.getStringPreferenceById(preferenceId); - const selector = this.getPreferenceEditorSelector(preferenceId); - const done = await resetPreferenceButton.click(); - await this.page.waitForFunction(data => { - const element = document.querySelector(data.selector); - if (!element) { - throw new Error(`Could not find preference element with id "${data.preferenceId}"`); - } - const value = (element as HTMLInputElement).value; - return value !== data.previousValue; - }, { preferenceId, selector, previousValue, done }, { timeout: this.customTimeout }); - } - - async resetStringPreferenceByPath(sectionTitle: string, name: string): Promise { + async setOptionsPreferenceByPath(sectionTitle: string, name: string, value: string): Promise { const preferenceId = await this.findPreferenceId(sectionTitle, name); - return this.resetStringPreferenceById(preferenceId); + return this.setOptionsPreferenceById(preferenceId, value); } - async resetBooleanPreferenceById(preferenceId: string): Promise { - const resetPreferenceButton = await this.findPreferenceResetButton(preferenceId); - if (!resetPreferenceButton) { - // preference not modified - return; - } - const previousValue = await this.getBooleanPreferenceById(preferenceId); - const selector = this.getPreferenceEditorSelector(preferenceId); - const done = await resetPreferenceButton.click(); - await this.page.waitForFunction(data => { - const element = document.querySelector(data.selector); - if (!element) { - throw new Error(`Could not find preference element with id "${data.preferenceId}"`); - } - const value = (element as HTMLInputElement).checked; - return value !== data.previousValue; - }, { preferenceId, selector, previousValue, done }, { timeout: this.customTimeout }); - } - - async resetBooleanPreferenceByPath(sectionTitle: string, name: string): Promise { + async setOptionsPreferenceById(preferenceId: string, value: string): Promise { + const element = await this.findPreferenceEditorById(preferenceId, this.optionSelectLabel); + await element.click(); + const option = await this.page.waitForSelector(`${this.optionSelectDropdown} ${this.optionSelectDropdownValue}:has-text("${value}")`); + await option.click(); + } + + async resetPreferenceByPath(sectionTitle: string, name: string): Promise { const preferenceId = await this.findPreferenceId(sectionTitle, name); - return this.resetBooleanPreferenceById(preferenceId); + return this.resetPreferenceById(preferenceId); + } + + async resetPreferenceById(preferenceId: string): Promise { + // this is just to fail if the preference doesn't exist at all + await this.findPreferenceEditorById(preferenceId, ''); + const resetPreferenceButton = await this.findPreferenceResetButton(preferenceId); + await resetPreferenceButton.click(); + await this.waitForUnmodified(preferenceId); } private async findPreferenceId(sectionTitle: string, name: string): Promise { @@ -178,9 +182,9 @@ export class TheiaPreferenceView extends TheiaView { return preferenceId; } - private async findPreferenceEditorById(preferenceId: string): Promise> { + private async findPreferenceEditorById(preferenceId: string, elementType: string = 'input'): Promise> { const viewElement = await this.viewElement(); - const element = await viewElement?.waitForSelector(this.getPreferenceEditorSelector(preferenceId), { timeout: this.customTimeout }); + const element = await viewElement?.waitForSelector(this.getPreferenceEditorSelector(preferenceId, elementType), { timeout: this.customTimeout }); if (!element) { throw new Error(`Could not find element with preference id "${preferenceId}"`); } @@ -191,26 +195,13 @@ export class TheiaPreferenceView extends TheiaView { return `li[data-pref-id="${preferenceId}"]`; } - private getPreferenceEditorSelector(preferenceId: string): string { - return `${this.getPreferenceSelector(preferenceId)} input`; + private getPreferenceEditorSelector(preferenceId: string, elementType: string): string { + return `${this.getPreferenceSelector(preferenceId)} ${elementType}`; } - private getPreferenceGutterSelector(preferenceId: string): string { - return `${this.getPreferenceSelector(preferenceId)} .pref-context-gutter`; - } - - private async findPreferenceResetButton(preferenceId: string): Promise | undefined> { + private async findPreferenceResetButton(preferenceId: string): Promise> { await this.activate(); const viewElement = await this.viewElement(); - const gutter = await viewElement?.waitForSelector(`${this.getPreferenceGutterSelector(preferenceId)}`, { timeout: this.customTimeout }); - if (!gutter) { - throw new Error(`Could not determine modified state for element with preference id "${preferenceId}"`); - } - const isModified = await gutter.evaluate(e => e.classList.contains('theia-mod-item-modified')); - if (!isModified) { - return undefined; - } - const settingsContextMenuBtn = await viewElement?.waitForSelector(`${this.getPreferenceSelector(preferenceId)} .settings-context-menu-btn`); if (!settingsContextMenuBtn) { throw new Error(`Could not find context menu button for element with preference id "${preferenceId}"`); @@ -223,4 +214,19 @@ export class TheiaPreferenceView extends TheiaView { return resetPreferenceButton; } + async waitForModified(preferenceId: string): Promise { + await this.activate(); + const viewElement = await this.viewElement(); + await viewElement?.waitForSelector(`${this.getPreferenceGutterSelector(preferenceId)}${this.modificationIndicator}`, { timeout: this.customTimeout }); + } + + async waitForUnmodified(preferenceId: string): Promise { + await this.activate(); + const viewElement = await this.viewElement(); + await viewElement?.waitForSelector(`${this.getPreferenceGutterSelector(preferenceId)}${this.modificationIndicator}`, { state: 'detached', timeout: this.customTimeout }); + } + + private getPreferenceGutterSelector(preferenceId: string): string { + return `${this.getPreferenceSelector(preferenceId)} .pref-context-gutter`; + } }