diff --git a/appveyor-woa.yml b/appveyor-woa.yml index efe6ce71f2a..567a0c171e8 100644 --- a/appveyor-woa.yml +++ b/appveyor-woa.yml @@ -45,10 +45,16 @@ environment: matrix: - job_name: Build Arm on X64 Windows - - job_name: Test On Windows On Arm Hardware + - job_name: Test On Windows On Arm Hardware 1 job_depends_on: Build Arm on X64 Windows APPVEYOR_BUILD_WORKER_IMAGE: base-woa APPVEYOR_BUILD_WORKER_CLOUD: electronhq-woa + shard: 1 + - job_name: Test On Windows On Arm Hardware 2 + job_depends_on: Build Arm on X64 Windows + APPVEYOR_BUILD_WORKER_IMAGE: base-woa + APPVEYOR_BUILD_WORKER_CLOUD: electronhq-woa + shard: 2 clone_script: - ps: git clone -q $("--branch=" + $Env:APPVEYOR_REPO_BRANCH) $("https://github.com/" + $Env:APPVEYOR_REPO_NAME + ".git") $Env:APPVEYOR_BUILD_FOLDER @@ -254,7 +260,8 @@ for: } - matrix: only: - - job_name: Test On Windows On Arm Hardware + - job_name: Test On Windows On Arm Hardware 1 + - job_name: Test On Windows On Arm Hardware 2 environment: IGNORE_YARN_INSTALL_ERROR: 1 @@ -326,7 +333,9 @@ for: if ($env:TARGET_ARCH -eq 'ia32') { $env:npm_config_arch = "ia32" } - - echo Running main test suite & node script/yarn test --runners=main --enable-logging --disable-features=CalculateNativeWinOcclusion + - ps: $env:tests_files=node script\split-tests $env:shard 2 + - echo "Running shard %shard% specs %tests_files%" + - echo Running main test suite & node script/yarn test --runners=main --enable-logging --disable-features=CalculateNativeWinOcclusion --files %tests_files% - cd .. - echo Verifying non proprietary ffmpeg & python electron\script\verify-ffmpeg.py --build-dir out\Default --source-root %cd% --ffmpeg-path out\ffmpeg diff --git a/appveyor.yml b/appveyor.yml index 8231c28d05f..d6585708736 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -45,8 +45,12 @@ environment: matrix: - job_name: Build - - job_name: Test + - job_name: Test 1 job_depends_on: Build + shard: 1 + - job_name: Test 2 + job_depends_on: Build + shard: 2 clone_script: - ps: git clone -q $("--branch=" + $Env:APPVEYOR_REPO_BRANCH) $("https://github.com/" + $Env:APPVEYOR_REPO_NAME + ".git") $Env:APPVEYOR_BUILD_FOLDER @@ -246,7 +250,8 @@ for: } - matrix: only: - - job_name: Test + - job_name: Test 1 + - job_name: Test 2 environment: DD_ENV: ci @@ -316,7 +321,9 @@ for: if ($env:TARGET_ARCH -eq 'ia32') { $env:npm_config_arch = "ia32" } - - echo Running main test suite & node script/yarn test -- --trace-uncaught --runners=main --enable-logging + - ps: $env:tests_files=node script\split-tests $env:shard 2 + - echo "Running shard %shard% specs %tests_files%" + - echo Running main test suite & node script/yarn test -- --trace-uncaught --runners=main --enable-logging --files %tests_files% - cd .. - echo Verifying non proprietary ffmpeg & python electron\script\verify-ffmpeg.py --build-dir out\Default --source-root %cd% --ffmpeg-path out\ffmpeg - echo "About to verify mksnapshot" diff --git a/script/split-tests.js b/script/split-tests.js index d91fa207092..b31c0934d9f 100644 --- a/script/split-tests.js +++ b/script/split-tests.js @@ -1,6 +1,9 @@ const glob = require('glob'); const fs = require('node:fs'); +const path = require('node:path'); + +const VISIBILITY_SPEC = 'spec\\visibility-state-spec.ts'; const currentShard = parseInt(process.argv[2], 10); const shardCount = parseInt(process.argv[3], 10); @@ -25,9 +28,16 @@ specFiles.sort((a, b) => { let shard = 0; for (const specFile of specFiles) { - buckets[shard].push(specFile); + buckets[shard].push(path.normalize(specFile)); shard++; if (shard === shardCount) shard = 0; } +const visiblitySpecIdx = buckets[currentShard - 1]; +if (visiblitySpecIdx > -1) { + // If visibility-state-spec is in the list, move it to the first position + // so that it gets executed first to avoid other specs interferring with it. + buckets[currentShard - 1].splice(visiblitySpecIdx, 1); + buckets[currentShard - 1].unshift(VISIBILITY_SPEC); +} console.log(buckets[currentShard - 1].join(' ')); diff --git a/spec/fixtures/chromium/visibilitystate.html b/spec/fixtures/chromium/visibilitystate.html index 7b1576b9d7e..5e0f97540e1 100644 --- a/spec/fixtures/chromium/visibilitystate.html +++ b/spec/fixtures/chromium/visibilitystate.html @@ -7,12 +7,6 @@ Document - +

Visibility Test

diff --git a/spec/visibility-state-spec.ts b/spec/visibility-state-spec.ts index 2f23420d803..9a248ef4bf7 100644 --- a/spec/visibility-state-spec.ts +++ b/spec/visibility-state-spec.ts @@ -1,26 +1,37 @@ -import { BaseWindow, BrowserWindow, BrowserWindowConstructorOptions, ipcMain, WebContents, WebContentsView } from 'electron/main'; +import { BaseWindow, BrowserWindow, BrowserWindowConstructorOptions, WebContents, WebContentsView } from 'electron/main'; import { expect } from 'chai'; import * as cp from 'node:child_process'; import { once } from 'node:events'; import * as path from 'node:path'; -import { setTimeout } from 'node:timers/promises'; -import { ifdescribe } from './lib/spec-helpers'; -import { closeWindow } from './lib/window-helpers'; +import { ifdescribe, waitUntil } from './lib/spec-helpers'; +import { closeAllWindows } from './lib/window-helpers'; // visibilityState specs pass on linux with a real window manager but on CI // the environment does not let these specs pass ifdescribe(process.platform !== 'linux')('document.visibilityState', () => { let w: BaseWindow & {webContents: WebContents}; - afterEach(() => { - return closeWindow(w); + before(() => { + for (const checkWin of BaseWindow.getAllWindows()) { + console.log('WINDOW EXISTS BEFORE TEST STARTED:', checkWin.title, checkWin.id); + } + }); + + afterEach(async () => { + await closeAllWindows(); + w = null as unknown as BrowserWindow; }); const load = () => w.webContents.loadFile(path.resolve(__dirname, 'fixtures', 'chromium', 'visibilitystate.html')); + async function haveVisibilityState (state: string) { + const docVisState = await w.webContents.executeJavaScript('document.visibilityState'); + return docVisState === state; + } + const itWithOptions = (name: string, options: BrowserWindowConstructorOptions, fn: Mocha.Func) => { it(name, async function (...args) { w = new BrowserWindow({ @@ -32,6 +43,9 @@ ifdescribe(process.platform !== 'linux')('document.visibilityState', () => { contextIsolation: false } }); + if (options.show && process.platform === 'darwin') { + await once(w, 'show'); + } await Promise.resolve(fn.apply(this, args)); }); @@ -42,30 +56,30 @@ ifdescribe(process.platform !== 'linux')('document.visibilityState', () => { const wcv = new WebContentsView({ webPreferences: { ...(options.webPreferences ?? {}), nodeIntegration: true, contextIsolation: false } }); baseWindow.contentView = wcv; w = Object.assign(baseWindow, { webContents: wcv.webContents }); + if (options.show && process.platform === 'darwin') { + await once(w, 'show'); + } await Promise.resolve(fn.apply(this, args)); }); }; itWithOptions('should be visible when the window is initially shown by default', {}, async () => { load(); - const [, state] = await once(ipcMain, 'initial-visibility-state'); - expect(state).to.equal('visible'); + await expect(waitUntil(async () => await haveVisibilityState('visible'))).to.eventually.be.fulfilled(); }); itWithOptions('should be visible when the window is initially shown', { show: true }, async () => { load(); - const [, state] = await once(ipcMain, 'initial-visibility-state'); - expect(state).to.equal('visible'); + await expect(waitUntil(async () => await haveVisibilityState('visible'))).to.eventually.be.fulfilled(); }); itWithOptions('should be hidden when the window is initially hidden', { show: false }, async () => { load(); - const [, state] = await once(ipcMain, 'initial-visibility-state'); - expect(state).to.equal('hidden'); + await expect(waitUntil(async () => await haveVisibilityState('hidden'))).to.eventually.be.fulfilled(); }); itWithOptions('should be visible when the window is initially hidden but shown before the page is loaded', { @@ -73,52 +87,40 @@ ifdescribe(process.platform !== 'linux')('document.visibilityState', () => { }, async () => { w.show(); load(); - const [, state] = await once(ipcMain, 'initial-visibility-state'); - expect(state).to.equal('visible'); + await expect(waitUntil(async () => await haveVisibilityState('visible'))).to.eventually.be.fulfilled(); }); itWithOptions('should be hidden when the window is initially shown but hidden before the page is loaded', { show: true }, async () => { - // TODO(MarshallOfSound): Figure out if we can work around this 1 tick issue for users - if (process.platform === 'darwin') { - // Wait for a tick, the window being "shown" takes 1 tick on macOS - await setTimeout(10000); - } w.hide(); load(); - const [, state] = await once(ipcMain, 'initial-visibility-state'); - expect(state).to.equal('hidden'); + await expect(waitUntil(async () => await haveVisibilityState('hidden'))).to.eventually.be.fulfilled(); }); itWithOptions('should be toggle between visible and hidden as the window is hidden and shown', {}, async () => { load(); - const [, initialState] = await once(ipcMain, 'initial-visibility-state'); - expect(initialState).to.equal('visible'); + await expect(waitUntil(async () => await haveVisibilityState('visible'))).to.eventually.be.fulfilled(); w.hide(); - await once(ipcMain, 'visibility-change-hidden'); + await expect(waitUntil(async () => await haveVisibilityState('hidden'))).to.eventually.be.fulfilled(); w.show(); - await once(ipcMain, 'visibility-change-visible'); + await expect(waitUntil(async () => await haveVisibilityState('visible'))).to.eventually.be.fulfilled(); }); itWithOptions('should become hidden when a window is minimized', {}, async () => { load(); - const [, initialState] = await once(ipcMain, 'initial-visibility-state'); - expect(initialState).to.equal('visible'); + await expect(waitUntil(async () => await haveVisibilityState('visible'))).to.eventually.be.fulfilled(); w.minimize(); - const p = once(ipcMain, 'visibility-change-hidden'); - w.minimize(); - await p; + await expect(waitUntil(async () => await haveVisibilityState('hidden'))).to.eventually.be.fulfilled(); }); itWithOptions('should become visible when a window is restored', {}, async () => { load(); - const [, initialState] = await once(ipcMain, 'initial-visibility-state'); - expect(initialState).to.equal('visible'); + await expect(waitUntil(async () => await haveVisibilityState('visible'))).to.eventually.be.fulfilled(); w.minimize(); - await once(ipcMain, 'visibility-change-hidden'); + await expect(waitUntil(async () => await haveVisibilityState('hidden'))).to.eventually.be.fulfilled(); w.restore(); - await once(ipcMain, 'visibility-change-visible'); + await expect(waitUntil(async () => await haveVisibilityState('visible'))).to.eventually.be.fulfilled(); }); ifdescribe(process.platform === 'darwin')('on platforms that support occlusion detection', () => { @@ -152,8 +154,7 @@ ifdescribe(process.platform !== 'linux')('document.visibilityState', () => { height: 200 }); load(); - const [, state] = await once(ipcMain, 'initial-visibility-state'); - expect(state).to.equal('visible'); + await expect(waitUntil(async () => await haveVisibilityState('visible'))).to.eventually.be.fulfilled(); }); itWithOptions('should be visible when two windows are on screen that overlap partially', { @@ -169,8 +170,7 @@ ifdescribe(process.platform !== 'linux')('document.visibilityState', () => { height: 200 }); load(); - const [, state] = await once(ipcMain, 'initial-visibility-state'); - expect(state).to.equal('visible'); + await expect(waitUntil(async () => await haveVisibilityState('visible'))).to.eventually.be.fulfilled(); }); itWithOptions('should be hidden when a second window completely occludes the current window', { @@ -181,15 +181,14 @@ ifdescribe(process.platform !== 'linux')('document.visibilityState', () => { }, async function () { this.timeout(240000); load(); - const [, state] = await once(ipcMain, 'initial-visibility-state'); - expect(state).to.equal('visible'); + await expect(waitUntil(async () => await haveVisibilityState('visible'))).to.eventually.be.fulfilled(); makeOtherWindow({ x: 0, y: 0, width: 300, height: 300 }); - await once(ipcMain, 'visibility-change-hidden'); + await expect(waitUntil(async () => await haveVisibilityState('hidden'))).to.eventually.be.fulfilled(); }); }); });