eeschema copy/paste fix: wx UTF-8 text dataobj (c1f1477) + regression specs
Cmd+C/Cmd+V in the wasm eeschema pasted a stray "(" SCH_TEXT — the stroke
font renders it as a small blue arc — instead of the copied symbol, and any
copy reached navigator.clipboard as a single character. Root cause and fix
live in wxwidgets (90bbad29822: UTF-8 wxDF_UNICODETEXT + the port's missing
GetAllFormats key function; c1f14775ba3: empty-read cache fallback + no
browser pre-clear on copy). This bump carries them plus the guards:
- tests/kicad/eeschema-copy-paste.spec.ts: chromium asserts the full
multi-form (lib_symbols …)(symbol …) blob reaches navigator.clipboard;
both engines assert copy → paste → save yields a second symbol and no
stray (text …) item (firefox exercises the m_textCache fallback path).
The kicad-chromium project now grants clipboard-read/write. NB the kicad
projects' device UAs claim Windows, so specs must send plain Control+…
— ControlOrMeta resolves to Meta on a mac host, which the "Windows" app
ignores, and the bare key fires eeschema hotkeys instead.
- tests/e2e/textdataobj.spec.ts + Makefile.wasm target `textdataobj`:
drives the wx-repo harness app (wxwidgets/tests/wasm/textdataobj_test.cpp)
and asserts the SUITE-DONE failure counters plus the full-string browser
round-trip, sentinel-seeded so a failed write cannot read as stale success.
- tests/e2e/clipboard.spec.ts: the Copy test now asserts the full input
text reaches the clipboard (previously truncated to "S").
Verified locally: all specs red on the old wx, green after; wx-chromium
320/320, kicad-chromium 121/121 (collab specs env-gated).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
fcad12d251
commit
6034a0550d
6 changed files with 324 additions and 2 deletions
|
|
@ -170,6 +170,7 @@ all: minimal_test.html \
|
|||
$(S)/scrollbar/scrollbar_test.html \
|
||||
$(S)/notebook/notebook_test.html \
|
||||
$(S)/clipboard/clipboard_test.html \
|
||||
$(S)/textdataobj/textdataobj_test.html \
|
||||
$(S)/filedialog/filedialog_test.html \
|
||||
$(S)/layout/layout_test.html \
|
||||
$(S)/aui/aui_test.html \
|
||||
|
|
@ -321,6 +322,18 @@ $(S)/clipboard/clipboard_test.o: $(S)/clipboard/clipboard_test.cpp
|
|||
$(S)/clipboard/clipboard_test.html: $(S)/clipboard/clipboard_test.o $(WX_CORE_LIB) $(JS_FILES)
|
||||
$(CXX) $< $(LDFLAGS_NOGL) --pre-js $(JS) --shell-file $(HTML) -o $@
|
||||
|
||||
# wxTextDataObject encoding test (no GL). The source lives in the wxWidgets
|
||||
# repo — the regression under test is a wx bug (wxNEEDS_UTF8_FOR_TEXT_DATAOBJ
|
||||
# on the wasm port), so the test case travels with it.
|
||||
WX_TESTS_WASM_DIR = ../../wxwidgets/tests/wasm
|
||||
|
||||
$(S)/textdataobj/textdataobj_test.o: $(WX_TESTS_WASM_DIR)/textdataobj_test.cpp
|
||||
@mkdir -p $(@D)
|
||||
$(CXX) -c $(CXXFLAGS) $< -o $@
|
||||
|
||||
$(S)/textdataobj/textdataobj_test.html: $(S)/textdataobj/textdataobj_test.o $(WX_CORE_LIB) $(JS_FILES)
|
||||
$(CXX) $< $(LDFLAGS_NOGL) --pre-js $(JS) --shell-file $(HTML) -o $@
|
||||
|
||||
# FileDialog test (no GL)
|
||||
$(S)/filedialog/filedialog_test.o: $(S)/filedialog/filedialog_test.cpp
|
||||
$(CXX) -c $(CXXFLAGS) $< -o $@
|
||||
|
|
@ -776,6 +789,7 @@ contextmenu: $(S)/contextmenu/contextmenu_test.html
|
|||
scrollbar: $(S)/scrollbar/scrollbar_test.html
|
||||
notebook: $(S)/notebook/notebook_test.html
|
||||
clipboard: $(S)/clipboard/clipboard_test.html
|
||||
textdataobj: $(S)/textdataobj/textdataobj_test.html
|
||||
filedialog: $(S)/filedialog/filedialog_test.html
|
||||
layout: $(S)/layout/layout_test.html
|
||||
aui: $(S)/aui/aui_test.html
|
||||
|
|
@ -850,7 +864,7 @@ clean:
|
|||
rm -f $(S)/*/*_test*.html $(S)/*/*_test*.js $(S)/*/*_test*.wasm
|
||||
rm -f $(S)/*/*_repro*.html $(S)/*/*_repro*.js $(S)/*/*_repro*.wasm
|
||||
|
||||
.PHONY: all clean menu contextmenu scrollbar clipboard filedialog layout aui toolbar grid dialog timer tree dataview htmlwin stc print dnd propgrid pickers collapsible listctrl infobar dataviewvirtual auinotebook wizard gridedit calendar gridrenderers printpreview bitmapbuttons specialized validators ownerdrawn popup xml wasmedge fontenum textdecor bitmask regions maximize earlysize selectheight uipolish threadpool logerror retinascale coroutine coroutine-nested asyncify-races notebook radiogroups collapse-relayout
|
||||
.PHONY: all clean menu contextmenu scrollbar clipboard textdataobj filedialog layout aui toolbar grid dialog timer tree dataview htmlwin stc print dnd propgrid pickers collapsible listctrl infobar dataviewvirtual auinotebook wizard gridedit calendar gridrenderers printpreview bitmapbuttons specialized validators ownerdrawn popup xml wasmedge fontenum textdecor bitmask regions maximize earlysize selectheight uipolish threadpool logerror retinascale coroutine coroutine-nested asyncify-races notebook radiogroups collapse-relayout
|
||||
|
||||
# === Coroutine pthread variant — historically reproduced the KiCad coroutine x pthreads crash ===
|
||||
# Same modal-free harness as `coroutine`, but compiled/linked with pthreads to match
|
||||
|
|
|
|||
|
|
@ -22,6 +22,9 @@ test.describe('wxClipboard Tests', () => {
|
|||
await page.goto('/standalone/clipboard/clipboard_test.html');
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Seed the clipboard so a silently-failed write cannot pass as success.
|
||||
await page.evaluate(() => navigator.clipboard.writeText('SENTINEL-BEFORE-COPY'));
|
||||
|
||||
// Click "Copy to Clipboard" button
|
||||
await clickByLabel(page, 'Copy to Clipboard');
|
||||
|
||||
|
|
@ -30,6 +33,11 @@ test.describe('wxClipboard Tests', () => {
|
|||
(l.includes('SUCCESS') && l.includes('Copied')) || l.includes('Attempting to copy')
|
||||
), { message: 'Copy should succeed or at least attempt' }).toBe(true);
|
||||
|
||||
// The FULL input text must reach the browser clipboard — the UTF-32/UTF-8
|
||||
// mixup in wxTextDataObject used to truncate it to its first character.
|
||||
const clip = await page.evaluate(() => navigator.clipboard.readText());
|
||||
expect(clip).toBe('Sample text for clipboard test');
|
||||
|
||||
await stableShot(page, 'clipboard-02-copy-clicked.png', { fullPage: true });
|
||||
});
|
||||
|
||||
|
|
|
|||
77
tests/e2e/textdataobj.spec.ts
Normal file
77
tests/e2e/textdataobj.spec.ts
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
// wxTextDataObject UTF-8 encoding regression (wasm clipboard truncation).
|
||||
//
|
||||
// Drives wxwidgets/tests/wasm/textdataobj_test.cpp (built by Makefile.wasm
|
||||
// target `textdataobj`). The bug: without wxNEEDS_UTF8_FOR_TEXT_DATAOBJ the
|
||||
// port's wxTextDataObject served UTF-32 for wxDF_UNICODETEXT; the wasm
|
||||
// wxClipboard read it as UTF-8 and the JS write helper truncated at the first
|
||||
// NUL — navigator.clipboard got exactly ONE character of any copy. In KiCad
|
||||
// that surfaced as eeschema pasting a stray "(" SCH_TEXT (rendered as a small
|
||||
// blue arc) instead of the copied symbol.
|
||||
//
|
||||
// The C++ side logs [WXTEST] PASS/FAIL lines for the wx-API-level contract;
|
||||
// this spec asserts none failed AND that the browser clipboard ends up with
|
||||
// the complete multi-line, non-ASCII string.
|
||||
import { test, expect, waitForWxApp, clickByLabel } from './utils/fixtures';
|
||||
|
||||
// Must match TEST_UTF8 in wxwidgets/tests/wasm/textdataobj_test.cpp byte for byte.
|
||||
const TEST_STRING = [
|
||||
'(kicad_sch (version 20250114)',
|
||||
' (symbol (lib_id "Device:R") (value "10kµΩ¶→"))',
|
||||
')',
|
||||
].join('\n');
|
||||
|
||||
test.describe('wxTextDataObject UTF-8 encoding', () => {
|
||||
test('serves UTF-8 for wxDF_UNICODETEXT and round-trips the clipboard in full', async ({
|
||||
page,
|
||||
testLogger,
|
||||
}) => {
|
||||
await page.goto('/standalone/textdataobj/textdataobj_test.html');
|
||||
await waitForWxApp(page);
|
||||
|
||||
// Startup suite: pure data-object assertions (size/bytes/composite/SetData).
|
||||
await expect.poll(
|
||||
() => testLogger.consoleLogs.some(l => l.includes('[WXTEST] SUITE-DONE startup')),
|
||||
{ message: 'startup checks should complete' }
|
||||
).toBe(true);
|
||||
|
||||
// Seed the clipboard so a silently-failed write cannot pass as success.
|
||||
await page.evaluate(() => navigator.clipboard.writeText('SENTINEL-BEFORE-COPY'));
|
||||
|
||||
// The wxClipboard round-trip suspends via JSPI, so it runs from a button
|
||||
// handler — the port's supported suspension context.
|
||||
expect(await clickByLabel(page, 'Run Clipboard RoundTrip')).toBe(true);
|
||||
await expect.poll(
|
||||
() => testLogger.consoleLogs.some(l => l.includes('[WXTEST] SUITE-DONE clipboard')),
|
||||
{ message: 'clipboard round-trip should complete' }
|
||||
).toBe(true);
|
||||
|
||||
// Browser-level contract: the FULL text reached navigator.clipboard
|
||||
// (the original bug left exactly one character here).
|
||||
const clip = await page.evaluate(() => navigator.clipboard.readText());
|
||||
expect(clip).toBe(TEST_STRING);
|
||||
|
||||
// Empty the browser clipboard, then drive a wx paste: it must fall back
|
||||
// to wxClipboard's local text cache (the round-trip copy) instead of
|
||||
// reporting the empty browser read as the result.
|
||||
await page.evaluate(() => navigator.clipboard.writeText(''));
|
||||
expect(await clickByLabel(page, 'Run Cache Fallback')).toBe(true);
|
||||
await expect.poll(
|
||||
() => testLogger.consoleLogs.some(l => l.includes('[WXTEST] SUITE-DONE fallback')),
|
||||
{ message: 'cache-fallback check should complete' }
|
||||
).toBe(true);
|
||||
|
||||
// wx-level contract: every [WXTEST] assertion passed. Assert on the
|
||||
// SUITE-DONE failure COUNTERS, not just the absence of FAIL lines — a
|
||||
// FAIL line that never reaches the console must not read as a pass.
|
||||
expect(testLogger.consoleLogs.filter(l => l.includes('[WXTEST] FAIL'))).toEqual([]);
|
||||
const counters = testLogger.consoleLogs
|
||||
.map(l => l.match(/\[WXTEST\] SUITE-DONE \w+ failures=(\d+)/))
|
||||
.filter((m): m is RegExpMatchArray => !!m)
|
||||
.map(m => parseInt(m[1], 10));
|
||||
expect(counters.length).toBe(3);
|
||||
expect(counters).toEqual([0, 0, 0]);
|
||||
|
||||
// (The full-text browser assertion ran above, before the fallback step
|
||||
// deliberately emptied navigator.clipboard.)
|
||||
});
|
||||
});
|
||||
220
tests/kicad/eeschema-copy-paste.spec.ts
Normal file
220
tests/kicad/eeschema-copy-paste.spec.ts
Normal file
|
|
@ -0,0 +1,220 @@
|
|||
import { test, expect } from './fixtures';
|
||||
import { stableShot } from '../e2e/utils/element-tracker';
|
||||
|
||||
/**
|
||||
* Eeschema copy/paste regression (wasm clipboard truncation).
|
||||
*
|
||||
* The bug: without wxNEEDS_UTF8_FOR_TEXT_DATAOBJ the wasm port's
|
||||
* wxTextDataObject served UTF-32 for wxDF_UNICODETEXT; wxClipboard::AddData
|
||||
* read it as UTF-8 (ASCII-in-UTF-32 = "valid" UTF-8 full of NULs) and the JS
|
||||
* write helper's UTF8ToString truncated at the first NUL. Cmd+C left exactly
|
||||
* "(" on navigator.clipboard; Cmd+V then failed the s-expression parse and
|
||||
* eeschema's fallback pasted a stray SCH_TEXT("(") — rendered by the stroke
|
||||
* font as a small blue arc instead of the copied symbol.
|
||||
*
|
||||
* Two layers:
|
||||
* - chromium-only: the copied s-expression must reach the browser clipboard
|
||||
* IN FULL (cross-instance copy/paste transport).
|
||||
* - both engines: copy → paste → save must yield a second symbol and no
|
||||
* stray text item. On firefox (no clipboard permission grants) the paste
|
||||
* exercises the wxClipboard m_textCache fallback; on chromium the real
|
||||
* browser clipboard — both transports covered.
|
||||
*
|
||||
* The wx-API-level contract has its own harness:
|
||||
* wxwidgets/tests/wasm/textdataobj_test.cpp + e2e/textdataobj.spec.ts.
|
||||
*/
|
||||
|
||||
const SAMPLE_SCH = `(kicad_sch
|
||||
\t(version 20250114)
|
||||
\t(generator "eeschema")
|
||||
\t(generator_version "9.0")
|
||||
\t(uuid "aaaaaaaa-1111-1111-1111-111111111111")
|
||||
\t(paper "A4")
|
||||
\t(lib_symbols
|
||||
\t\t(symbol "Device:R" (pin_numbers hide) (pin_names (offset 0)) (exclude_from_sim no) (in_bom yes) (on_board yes)
|
||||
\t\t\t(property "Reference" "R" (at 2.032 0 90) (effects (font (size 1.27 1.27))))
|
||||
\t\t\t(property "Value" "R" (at 0 0 90) (effects (font (size 1.27 1.27))))
|
||||
\t\t\t(property "Footprint" "" (at -1.778 0 90) (effects (font (size 1.27 1.27)) hide))
|
||||
\t\t\t(property "Datasheet" "~" (at 0 0 0) (effects (font (size 1.27 1.27)) hide))
|
||||
\t\t\t(symbol "R_0_1"
|
||||
\t\t\t\t(rectangle (start -1.016 -2.54) (end 1.016 2.54) (stroke (width 0.254) (type default)) (fill (type none)))
|
||||
\t\t\t)
|
||||
\t\t\t(symbol "R_1_1"
|
||||
\t\t\t\t(pin passive line (at 0 3.81 270) (length 1.27) (name "~" (effects (font (size 1.27 1.27)))) (number "1" (effects (font (size 1.27 1.27)))))
|
||||
\t\t\t\t(pin passive line (at 0 -3.81 90) (length 1.27) (name "~" (effects (font (size 1.27 1.27)))) (number "2" (effects (font (size 1.27 1.27)))))
|
||||
\t\t\t)
|
||||
\t\t)
|
||||
\t)
|
||||
\t(symbol (lib_id "Device:R") (at 127 95.25 0) (unit 1) (exclude_from_sim no) (in_bom yes) (on_board yes) (dnp no)
|
||||
\t\t(uuid "bbbbbbbb-2222-2222-2222-222222222222")
|
||||
\t\t(property "Reference" "R1" (at 129.54 94.615 0) (effects (font (size 1.27 1.27)) (justify left)))
|
||||
\t\t(property "Value" "10k" (at 129.54 96.52 0) (effects (font (size 1.27 1.27)) (justify left)))
|
||||
\t\t(property "Footprint" "" (at 125.222 95.25 90) (effects (font (size 1.27 1.27)) hide))
|
||||
\t\t(property "Datasheet" "~" (at 127 95.25 0) (effects (font (size 1.27 1.27)) hide))
|
||||
\t\t(pin "1" (uuid "cccccccc-0000-0000-0000-000000000001"))
|
||||
\t\t(pin "2" (uuid "cccccccc-0000-0000-0000-000000000002"))
|
||||
\t\t(instances
|
||||
\t\t\t(project "copypaste"
|
||||
\t\t\t\t(path "/aaaaaaaa-1111-1111-1111-111111111111" (reference "R1") (unit 1))
|
||||
\t\t\t)
|
||||
\t\t)
|
||||
\t)
|
||||
\t(sheet_instances (path "/" (page "1")))
|
||||
)
|
||||
`;
|
||||
|
||||
const SCH_PATH = '/home/kicad/documents/copypaste.kicad_sch';
|
||||
|
||||
type EmscriptenFS = {
|
||||
mkdirTree(path: string): void;
|
||||
writeFile(path: string, data: string): void;
|
||||
readFile(path: string): Uint8Array;
|
||||
};
|
||||
type KicadModule = { kicadOpenFile(path: string): unknown };
|
||||
type WxWindow = Window & { FS: EmscriptenFS; Module: KicadModule };
|
||||
|
||||
async function bootWithSchematic(page: import('@playwright/test').Page) {
|
||||
await page.goto('/kicad/eeschema.html');
|
||||
|
||||
// Same boot gates as eeschema-load.spec.ts: canvas, element registry,
|
||||
// the embind open hook, and a top-level Frame.
|
||||
await expect(page.locator('#canvas')).toBeVisible({ timeout: 90000 });
|
||||
await page.waitForFunction(() => !!window.wxElementRegistry, null, { timeout: 90000 });
|
||||
await page.waitForFunction(
|
||||
() =>
|
||||
typeof (window as unknown as { Module?: KicadModule }).Module?.kicadOpenFile ===
|
||||
'function',
|
||||
null,
|
||||
{ timeout: 90000 }
|
||||
);
|
||||
await page.waitForFunction(
|
||||
() =>
|
||||
!!window.wxElementRegistry &&
|
||||
window.wxElementRegistry
|
||||
.findAll({ visible: true })
|
||||
.some((e) => /Frame$/.test(e.typeName) || (e.name || '').endsWith('Frame')),
|
||||
null,
|
||||
{ timeout: 90000 }
|
||||
);
|
||||
|
||||
await page.evaluate(
|
||||
({ content, path }) => {
|
||||
const w = window as unknown as WxWindow;
|
||||
try {
|
||||
w.FS.mkdirTree('/home/kicad/documents');
|
||||
} catch {
|
||||
/* already exists */
|
||||
}
|
||||
w.FS.writeFile(path, content);
|
||||
w.Module.kicadOpenFile(path);
|
||||
},
|
||||
{ content: SAMPLE_SCH, path: SCH_PATH }
|
||||
);
|
||||
|
||||
await expect
|
||||
.poll(async () => page.title(), {
|
||||
message: 'schematic load did not complete (title stayed untitled)',
|
||||
timeout: 30000,
|
||||
intervals: [500],
|
||||
})
|
||||
.toMatch(/copypaste/i);
|
||||
|
||||
// Focus the schematic drawing area so hotkeys land in the editor. The
|
||||
// whole wx UI renders inside the one #canvas element — the left-hand
|
||||
// panels are wx widgets, so the click must land right of them
|
||||
// (drawing area ≈ x 340-1250, y 90-660 at the 1280x720 viewport).
|
||||
await page.mouse.click(700, 400);
|
||||
await page.waitForTimeout(500);
|
||||
}
|
||||
|
||||
// The kicad projects run under devices['Desktop Chrome'/'Desktop Firefox'],
|
||||
// whose user agents claim Windows; the wasm port's mac-detection follows the
|
||||
// UA (wxGetOsVersion → platformInfo.name), so the editor maps Control from
|
||||
// ctrlKey. Plain 'Control+…' is therefore correct on every host OS here —
|
||||
// 'ControlOrMeta' would send Meta on a mac host, which the "Windows" app
|
||||
// ignores (observed: 'a' alone opened the Place Symbol dialog).
|
||||
async function selectAllAndCopy(page: import('@playwright/test').Page) {
|
||||
await page.keyboard.press('Control+a');
|
||||
await page.waitForTimeout(800);
|
||||
await page.keyboard.press('Control+c');
|
||||
// The clipboard write suspends via JSPI (browser round-trip, 2s timeout
|
||||
// budget inside wxClipboard) — give it room before acting on the result.
|
||||
await page.waitForTimeout(2500);
|
||||
}
|
||||
|
||||
test.describe('Eeschema copy/paste', () => {
|
||||
test('Cmd+C puts the full schematic s-expression on the browser clipboard', async ({
|
||||
page,
|
||||
browserName,
|
||||
}) => {
|
||||
test.skip(browserName === 'firefox', 'firefox cannot grant clipboard read/write to tests');
|
||||
|
||||
await bootWithSchematic(page);
|
||||
|
||||
// Seed known clipboard state so a failed write cannot pass as success.
|
||||
await page.evaluate(() => navigator.clipboard.writeText('SENTINEL-BEFORE-COPY'));
|
||||
|
||||
await selectAllAndCopy(page);
|
||||
|
||||
const clip = await page.evaluate(() => navigator.clipboard.readText());
|
||||
// eeschema's clipboard blob is the multi-form shape
|
||||
// `(lib_symbols ...)\n(symbol ...)` (see pcbjam-shared items-wire.ts).
|
||||
// The original bug left exactly "(" here (UTF-32 truncated at its
|
||||
// first NUL by the clipboard write helper).
|
||||
expect(clip).toMatch(/^\(lib_symbols/);
|
||||
expect(clip).toContain('(symbol "Device:R"');
|
||||
expect(clip).toContain('(lib_id "Device:R")');
|
||||
});
|
||||
|
||||
test('copy → paste → save yields a second symbol and no stray text item', async ({
|
||||
page,
|
||||
}) => {
|
||||
await bootWithSchematic(page);
|
||||
|
||||
await selectAllAndCopy(page);
|
||||
await page.keyboard.press('Escape');
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
// Paste attaches the items to the cursor; click commits the placement.
|
||||
await page.mouse.move(500, 300);
|
||||
await page.keyboard.press('Control+v');
|
||||
await page.waitForTimeout(2500);
|
||||
await stableShot(page, 'eeschema-copy-paste-preview.png');
|
||||
await page.mouse.click(500, 300);
|
||||
await page.waitForTimeout(800);
|
||||
await page.keyboard.press('Escape');
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
await page.keyboard.press('Control+s');
|
||||
|
||||
await expect
|
||||
.poll(
|
||||
async () => {
|
||||
const content = await page.evaluate((path) => {
|
||||
const w = window as unknown as WxWindow;
|
||||
return new TextDecoder().decode(w.FS.readFile(path));
|
||||
}, SCH_PATH);
|
||||
return (content.match(/\(lib_id "Device:R"\)/g) || []).length;
|
||||
},
|
||||
{
|
||||
message:
|
||||
'saved schematic should contain the original AND the pasted symbol ' +
|
||||
'(the truncation bug pasted a stray "(" text item instead)',
|
||||
timeout: 20000,
|
||||
intervals: [1000],
|
||||
}
|
||||
)
|
||||
.toBe(2);
|
||||
|
||||
const content = await page.evaluate((path) => {
|
||||
const w = window as unknown as WxWindow;
|
||||
return new TextDecoder().decode(w.FS.readFile(path));
|
||||
}, SCH_PATH);
|
||||
|
||||
// The failure mode of the truncation bug: an unparseable clipboard
|
||||
// pastes as a SCH_TEXT (serialized as a (text …) node).
|
||||
expect(content).not.toContain('(text "');
|
||||
|
||||
await stableShot(page, 'eeschema-copy-paste-committed.png');
|
||||
});
|
||||
});
|
||||
|
|
@ -192,6 +192,9 @@ export default defineConfig({
|
|||
use: {
|
||||
...devices['Desktop Chrome'],
|
||||
viewport: { width: 1280, height: 720 },
|
||||
// eeschema-copy-paste asserts the copied s-expression reaches
|
||||
// navigator.clipboard in full (firefox cannot grant these).
|
||||
permissions: ['clipboard-read', 'clipboard-write'],
|
||||
...CHROMIUM_CI_ARGS,
|
||||
},
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in a new issue