mirror of
				https://github.com/microsoft/playwright.git
				synced 2025-06-26 21:40:17 +00:00 
			
		
		
		
	chore: allow toggling recorder/traceviewer color modes (#18718)
Fixes: https://github.com/microsoft/playwright/issues/18700
This commit is contained in:
		
							parent
							
								
									dfb4ad388a
								
							
						
					
					
						commit
						d5eb74fa5d
					
				@ -15,7 +15,9 @@
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import fs from 'fs';
 | 
			
		||||
import path from 'path';
 | 
			
		||||
import type { Page } from '../page';
 | 
			
		||||
import { registryDirectory } from '../registry';
 | 
			
		||||
import type { CRPage } from './crPage';
 | 
			
		||||
 | 
			
		||||
export async function installAppIcon(page: Page) {
 | 
			
		||||
@ -25,3 +27,25 @@ export async function installAppIcon(page: Page) {
 | 
			
		||||
    image: icon.toString('base64')
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function syncLocalStorageWithSettings(page: Page, appName: string) {
 | 
			
		||||
  const settingsFile = path.join(registryDirectory, '.settings', `${appName}.json`);
 | 
			
		||||
  await page.exposeBinding('saveSettings', false, (_, settings: any) => {
 | 
			
		||||
    fs.mkdirSync(path.dirname(settingsFile), { recursive: true });
 | 
			
		||||
    fs.writeFileSync(settingsFile, settings);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const settings = await fs.promises.readFile(settingsFile, 'utf-8').catch(() => ('{}'));
 | 
			
		||||
  await page.addInitScript(`(${String((settings: any) => {
 | 
			
		||||
    Object.entries(settings).map(([k, v]) => localStorage[k] = v);
 | 
			
		||||
 | 
			
		||||
    let lastValue = JSON.stringify(localStorage);
 | 
			
		||||
    setInterval(() => {
 | 
			
		||||
      const value = JSON.stringify(localStorage);
 | 
			
		||||
      if (value !== lastValue) {
 | 
			
		||||
        lastValue = value;
 | 
			
		||||
        window.saveSettings(value);
 | 
			
		||||
      }
 | 
			
		||||
    }, 2000);
 | 
			
		||||
  })})(${settings})`);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -23,7 +23,7 @@ import { serverSideCallMetadata } from '../instrumentation';
 | 
			
		||||
import type { CallLog, EventData, Mode, Source } from '@recorder/recorderTypes';
 | 
			
		||||
import { isUnderTest } from '../../utils';
 | 
			
		||||
import { mime } from '../../utilsBundle';
 | 
			
		||||
import { installAppIcon } from '../chromium/crApp';
 | 
			
		||||
import { installAppIcon, syncLocalStorageWithSettings } from '../chromium/crApp';
 | 
			
		||||
import { findChromiumChannel } from '../registry';
 | 
			
		||||
import type { Recorder } from '../recorder';
 | 
			
		||||
import type { BrowserContext } from '../browserContext';
 | 
			
		||||
@ -37,6 +37,7 @@ declare global {
 | 
			
		||||
    playwrightSetSelector: (selector: string, focus?: boolean) => void;
 | 
			
		||||
    playwrightUpdateLogs: (callLogs: CallLog[]) => void;
 | 
			
		||||
    dispatch(data: EventData): Promise<void>;
 | 
			
		||||
    saveSettings(data: any): Promise<void>;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -79,6 +80,7 @@ export class RecorderApp extends EventEmitter implements IRecorderApp {
 | 
			
		||||
 | 
			
		||||
  private async _init() {
 | 
			
		||||
    await installAppIcon(this._page);
 | 
			
		||||
    await syncLocalStorageWithSettings(this._page, 'recorder');
 | 
			
		||||
 | 
			
		||||
    await this._page._setServerRequestInterceptor(async route => {
 | 
			
		||||
      if (route.request().url().startsWith('https://playwright/')) {
 | 
			
		||||
 | 
			
		||||
@ -21,7 +21,7 @@ import { HttpServer } from '../../../utils/httpServer';
 | 
			
		||||
import { findChromiumChannel } from '../../registry';
 | 
			
		||||
import { isUnderTest } from '../../../utils';
 | 
			
		||||
import type { BrowserContext } from '../../browserContext';
 | 
			
		||||
import { installAppIcon } from '../../chromium/crApp';
 | 
			
		||||
import { installAppIcon, syncLocalStorageWithSettings } from '../../chromium/crApp';
 | 
			
		||||
import { serverSideCallMetadata } from '../../instrumentation';
 | 
			
		||||
import { createPlaywright } from '../../playwright';
 | 
			
		||||
import { ProgressController } from '../../progress';
 | 
			
		||||
@ -81,7 +81,7 @@ export async function showTraceViewer(traceUrls: string[], browserName: string,
 | 
			
		||||
 | 
			
		||||
  if (traceViewerBrowser === 'chromium')
 | 
			
		||||
    await installAppIcon(page);
 | 
			
		||||
 | 
			
		||||
  await syncLocalStorageWithSettings(page, 'traceviewer');
 | 
			
		||||
 | 
			
		||||
  const params = traceUrls.map(t => `trace=${t}`);
 | 
			
		||||
  if (isUnderTest()) {
 | 
			
		||||
 | 
			
		||||
@ -24,6 +24,7 @@ import * as React from 'react';
 | 
			
		||||
import { CallLogView } from './callLog';
 | 
			
		||||
import './recorder.css';
 | 
			
		||||
import { asLocator } from '@isomorphic/locatorGenerators';
 | 
			
		||||
import { toggleTheme } from '@web/theme';
 | 
			
		||||
 | 
			
		||||
declare global {
 | 
			
		||||
  interface Window {
 | 
			
		||||
@ -129,6 +130,7 @@ export const Recorder: React.FC<RecorderProps> = ({
 | 
			
		||||
      <ToolbarButton icon='clear-all' title='Clear' disabled={!source || !source.text} onClick={() => {
 | 
			
		||||
        window.dispatch({ event: 'clear' });
 | 
			
		||||
      }}></ToolbarButton>
 | 
			
		||||
      <ToolbarButton icon='color-mode' title='Toggle color mode' toggled={false} onClick={() => toggleTheme()}></ToolbarButton>
 | 
			
		||||
    </Toolbar>
 | 
			
		||||
    <SplitView sidebarSize={200} sidebarHidden={mode === 'recording'}>
 | 
			
		||||
      <SourceView text={source.text} language={source.language} highlight={source.highlight} revealLine={source.revealLine}></SourceView>
 | 
			
		||||
 | 
			
		||||
@ -85,6 +85,11 @@
 | 
			
		||||
  color: white;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.workbench .header .toolbar-button {
 | 
			
		||||
  margin: 12px;
 | 
			
		||||
  padding: 8px 4px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.workbench tab-content {
 | 
			
		||||
  padding: 25px;
 | 
			
		||||
  contain: size;
 | 
			
		||||
 | 
			
		||||
@ -17,6 +17,7 @@
 | 
			
		||||
import type { ActionTraceEvent } from '@trace/trace';
 | 
			
		||||
import { SplitView } from '@web/components/splitView';
 | 
			
		||||
import { msToString } from '@web/uiUtils';
 | 
			
		||||
import { ToolbarButton } from '@web/components/toolbarButton';
 | 
			
		||||
import * as React from 'react';
 | 
			
		||||
import type { ContextEntry } from '../entries';
 | 
			
		||||
import { ActionList } from './actionList';
 | 
			
		||||
@ -30,6 +31,7 @@ import { SourceTab } from './sourceTab';
 | 
			
		||||
import { TabbedPane } from './tabbedPane';
 | 
			
		||||
import { Timeline } from './timeline';
 | 
			
		||||
import './workbench.css';
 | 
			
		||||
import { toggleTheme } from '@web/theme';
 | 
			
		||||
 | 
			
		||||
export const Workbench: React.FunctionComponent<{
 | 
			
		||||
}> = () => {
 | 
			
		||||
@ -156,6 +158,7 @@ export const Workbench: React.FunctionComponent<{
 | 
			
		||||
      <div className='product'>Playwright</div>
 | 
			
		||||
      {model.title && <div className='title'>{model.title}</div>}
 | 
			
		||||
      <div className='spacer'></div>
 | 
			
		||||
      <ToolbarButton icon='color-mode' title='Toggle color mode' toggled={false} onClick={() => toggleTheme()}></ToolbarButton>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div style={{ paddingLeft: '20px', flex: 'none', borderBottom: '1px solid var(--vscode-panel-border)' }}>
 | 
			
		||||
      <Timeline
 | 
			
		||||
 | 
			
		||||
@ -15,6 +15,10 @@
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
:root {
 | 
			
		||||
  color-scheme: light dark;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
body {
 | 
			
		||||
  --red: #F44336;
 | 
			
		||||
  --green: #367c39;
 | 
			
		||||
  --purple: #9C27B0;
 | 
			
		||||
@ -29,14 +33,12 @@
 | 
			
		||||
  --box-shadow: rgba(0, 0, 0, 0.133) 0px 1.6px 3.6px 0px, rgba(0, 0, 0, 0.11) 0px 0.3px 0.9px 0px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media(prefers-color-scheme: dark) {
 | 
			
		||||
  :root {
 | 
			
		||||
body.dark-mode {
 | 
			
		||||
  --green: #28d12f;
 | 
			
		||||
  --yellow: #ff9207;
 | 
			
		||||
  --purple: #dc12ff;
 | 
			
		||||
  --blue: #4dafff;
 | 
			
		||||
  --orange: #ff9800;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
html, body {
 | 
			
		||||
 | 
			
		||||
@ -25,4 +25,23 @@ export function applyTheme() {
 | 
			
		||||
  document!.defaultView!.addEventListener('blur', event => {
 | 
			
		||||
    document.body.classList.add('inactive');
 | 
			
		||||
  }, false);
 | 
			
		||||
 | 
			
		||||
  const currentTheme = localStorage.getItem('theme');
 | 
			
		||||
  const prefersDarkScheme = window.matchMedia('(prefers-color-scheme: dark)');
 | 
			
		||||
  if (currentTheme === 'dark-mode' || prefersDarkScheme.matches)
 | 
			
		||||
    document.body.classList.add('dark-mode');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function toggleTheme() {
 | 
			
		||||
  const oldTheme = localStorage.getItem('theme');
 | 
			
		||||
  let newTheme: string;
 | 
			
		||||
  if (oldTheme === 'dark-mode')
 | 
			
		||||
    newTheme = 'light-mode';
 | 
			
		||||
  else
 | 
			
		||||
    newTheme = 'dark-mode';
 | 
			
		||||
 | 
			
		||||
  if (oldTheme)
 | 
			
		||||
    document.body.classList.remove(oldTheme);
 | 
			
		||||
  document.body.classList.add(newTheme);
 | 
			
		||||
  localStorage.setItem('theme', newTheme);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -3,7 +3,7 @@
 | 
			
		||||
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 | 
			
		||||
 *--------------------------------------------------------------------------------------------*/
 | 
			
		||||
 | 
			
		||||
:root {
 | 
			
		||||
body {
 | 
			
		||||
  --vscode-font-family: system-ui, "Ubuntu", "Droid Sans", sans-serif;
 | 
			
		||||
  --vscode-font-weight: normal;
 | 
			
		||||
  --vscode-font-size: 13px;
 | 
			
		||||
@ -544,8 +544,7 @@
 | 
			
		||||
  --vscode-gitDecoration-submoduleResourceForeground: #1258a7;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media(prefers-color-scheme: dark) {
 | 
			
		||||
  :root {
 | 
			
		||||
body.dark-mode {
 | 
			
		||||
  --vscode-font-family: system-ui, "Ubuntu", "Droid Sans", sans-serif;
 | 
			
		||||
  --vscode-font-weight: normal;
 | 
			
		||||
  --vscode-font-size: 13px;
 | 
			
		||||
@ -1083,5 +1082,4 @@
 | 
			
		||||
  --vscode-gitDecoration-stageDeletedResourceForeground: #c74e39;
 | 
			
		||||
  --vscode-gitDecoration-conflictingResourceForeground: #e4676b;
 | 
			
		||||
  --vscode-gitDecoration-submoduleResourceForeground: #8db9e2;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user