Skip to content

Commit ab437a4

Browse files
committed
test(live-preview): fix flaky markdown and multibrowser live preview tests
- Hiding the panel stops Live Preview when no preview client is connected, and in a test window showing it again does not reopen it. The md close/reopen test and the pin open/hide test now reopen it as the app does, and the md cache group restarts it before each test so one test cannot fail every later one. - Related-CSS tests wait for the stylesheet's live document and read the browser copy until the edit arrives, instead of reading once. - Linked-CSS code hints follow the colors the menu offers instead of guessing the order, which depends on colors used earlier. - forRemoteExec only compares once a reply has arrived. It used to pass on the first poll with no result, so the resize test captured no width and the reload tests did not really check the reload. The resize test also looks up the preview iframe after it loads. - Replace fixed awaits() with condition waits in the touched tests.
1 parent f2f2382 commit ab437a4

2 files changed

Lines changed: 128 additions & 71 deletions

File tree

‎test/spec/LiveDevelopmentMultiBrowser-test.js‎

Lines changed: 105 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -109,14 +109,39 @@ define(function (require, exports, module) {
109109
return str.replace(allSpacesRE, " ");
110110
}
111111

112-
async function getSourceFromBrowser(liveDoc) {
113-
let doneSyncing = false, browserText;
114-
liveDoc.getSourceFromBrowser().done(function (text) {
115-
browserText = text;
116-
}).always(function () {
117-
doneSyncing = true;
118-
});
119-
await awaitsFor(function () { return doneSyncing; }, "Browser to sync changes", 20000);
112+
/**
113+
* A linked stylesheet gets its live document only once the page reports it back, which can
114+
* trail the page's own load. Wait for it rather than reading it straight away.
115+
* @return {Promise<Object>} the live document
116+
*/
117+
async function waitsForLiveDocForPath(fullPath) {
118+
await awaitsFor(function () {
119+
return !!LiveDevMultiBrowser.getLiveDocForPath(fullPath);
120+
}, "live document for " + fullPath, 20000);
121+
return LiveDevMultiBrowser.getLiveDocForPath(fullPath);
122+
}
123+
124+
/**
125+
* Read a live document back from the browser until it passes the check: an edit pushed
126+
* a moment ago may still be on its way.
127+
* @return {Promise<string>} the browser's text
128+
*/
129+
async function waitsForBrowserSource(liveDoc, isReady, message) {
130+
let browserText, reading = false;
131+
await awaitsFor(function () {
132+
if (browserText !== undefined && isReady(browserText)) {
133+
return true;
134+
}
135+
if (!reading) {
136+
reading = true;
137+
liveDoc.getSourceFromBrowser().done(function (text) {
138+
browserText = text;
139+
}).always(function () {
140+
reading = false;
141+
});
142+
}
143+
return false;
144+
}, message, 20000);
120145
return browserText;
121146
}
122147

@@ -196,6 +221,17 @@ define(function (require, exports, module) {
196221
editor && editor.setCursorPos({ line: 0, ch: 0 });
197222
}
198223

224+
/**
225+
* Hiding the panel closes Live Preview when no preview client is connected, and in a
226+
* test window showing the panel again does not reopen it: tests start Live Preview
227+
* themselves. Reopen it the way the app does, so the preview server comes back.
228+
*/
229+
function _reopenLiveDevIfStopped() {
230+
if (LiveDevMultiBrowser.status === LiveDevMultiBrowser.STATUS_INACTIVE) {
231+
LiveDevMultiBrowser.open();
232+
}
233+
}
234+
199235
async function waitsForLiveDevelopmentToOpen() {
200236
LiveDevMultiBrowser.open();
201237
try {
@@ -377,8 +413,10 @@ define(function (require, exports, module) {
377413
localText = curDoc.getText();
378414
localText += styleTextAdd;
379415
curDoc.setText(localText);
380-
liveDoc = LiveDevMultiBrowser.getLiveDocForPath(testFolder + "/simple1.css");
381-
browserText = await getSourceFromBrowser(liveDoc);
416+
liveDoc = await waitsForLiveDocForPath(testFolder + "/simple1.css");
417+
browserText = await waitsForBrowserSource(liveDoc, function (text) {
418+
return text.includes("testClass");
419+
}, "the edited rule to reach the browser");
382420
browserText = browserText.replace(/url\('http:\/\/127\.0\.0\.1:\d+\/import1\.css'\);/, "url('import1.css');");
383421

384422
expect(fixSpaces(browserText).includes(fixSpaces(styleTextAdd))).toBeTrue();
@@ -434,8 +472,10 @@ define(function (require, exports, module) {
434472
localText = curDoc.getText();
435473
localText += "\n .testClass { background-color:#090; }\n";
436474
curDoc.setText(localText);
437-
liveDoc = LiveDevMultiBrowser.getLiveDocForPath(testFolder + "/sub/test.css");
438-
browserText = await getSourceFromBrowser(liveDoc);
475+
liveDoc = await waitsForLiveDocForPath(testFolder + "/sub/test.css");
476+
browserText = await waitsForBrowserSource(liveDoc, function (text) {
477+
return text.includes("testClass");
478+
}, "the edited rule to reach the browser");
439479

440480
// Drop the port from 127.0.0.1:port so it's easier to work with
441481
browserText = browserText.replace(/127\.0\.0\.1:\d+/, "127.0.0.1");
@@ -705,44 +745,44 @@ define(function (require, exports, module) {
705745
"closing all files before opening simple1.css");
706746
await awaitsForDone(SpecRunnerUtils.openProjectFiles(["simple1.css"]),
707747
"simple1.css");
708-
const doc = DocumentManager.getCurrentDocument();
709-
const text = doc.getText();
710-
// The original simple1.css should NOT contain background-color:#090
711-
// That gets added by a previous test and must be cleaned up
712-
// We verify the file doesn't contain #090 background-color and if it does, change expectations
713-
// in linux, or if system slow, it will take some time for file system change event to catch
714-
// up and update document. so we need to do this below. This is a bug in tests as why is the test not
715-
// resetting file properly constantly?
716-
const has90 = text.includes("background-color:#090");
717-
const firstColor = has90 ? "#090" : "aliceblue";
718-
const firstColorRGB = has90 ? "rgb(0, 153, 0)" : "rgb(240, 248, 255)";
719-
720748
await _openCodeHints({ line: 3, ch: 8 }, ["antiquewhite"]);
721749

722750
let editor = EditorManager.getActiveEditor();
723751
const initialHistoryLength = editor.getHistory().done.length;
724-
SpecRunnerUtils.simulateKeyEvent(KeyEvent.DOM_VK_DOWN, "keydown", testWindow.document.body);
725-
await awaitsFor(function () {
726-
// #090 is the content from simple1.css file
727-
// this appears as the 2nd item in the codehint menu, from "suggest previously used color" feature
728-
return editor.getSelectedText() === firstColor;
729-
}, `expected live hints to update selection to ${firstColor}`);
730-
await _waitForLivePreviewElementColor("testId", firstColorRGB);
731-
SpecRunnerUtils.simulateKeyEvent(KeyEvent.DOM_VK_DOWN, "keydown", testWindow.document.body);
732-
733-
const secondColor = has90 ? "aliceblue" : "antiquewhite";
734-
const secondColorRGB = has90 ? "rgb(240, 248, 255)" : "rgb(250, 235, 215)";
735-
await awaitsFor(function () {
736-
return editor.getSelectedText() === secondColor;
737-
}, `expected live hints to update selection to ${secondColor}`);
752+
// Which colors come first is not fixed: the hints suggest colors used earlier, and an
753+
// earlier test's #090 can be among them. Follow whatever the menu offers instead.
754+
const firstColor = await _previewNextColorHint(editor, "");
755+
const secondColor = await _previewNextColorHint(editor, firstColor);
756+
return {historyLength: initialHistoryLength, lastColor: secondColor};
757+
}
738758

739-
await _waitForLivePreviewElementColor("testId", secondColorRGB); // antiquewhite
759+
/** The browser's computed form of a CSS color, e.g. "rgb(240, 248, 255)" for aliceblue. */
760+
function _computedColor(color) {
761+
const probe = testWindow.document.createElement("span");
762+
probe.style.color = color;
763+
testWindow.document.body.appendChild(probe);
764+
const computed = testWindow.getComputedStyle(probe).color;
765+
probe.remove();
766+
return computed;
767+
}
740768

741-
return initialHistoryLength;
769+
/**
770+
* Move to the next color hint: the editor shows it as the selection, and the page shows it.
771+
* @return {Promise<string>} the color previewed
772+
*/
773+
async function _previewNextColorHint(editor, previousColor) {
774+
SpecRunnerUtils.simulateKeyEvent(KeyEvent.DOM_VK_DOWN, "keydown", testWindow.document.body);
775+
let color;
776+
await awaitsFor(function () {
777+
color = editor.getSelectedText();
778+
return !!color && color !== previousColor;
779+
}, "live hints to preview the next color");
780+
await _waitForLivePreviewElementColor("testId", _computedColor(color));
781+
return color;
742782
}
743783

744784
it("should Live preview push css code hints selection changes to browser(linked css)", async function () {
745-
const expectedHistoryLength = await _livePreviewCodeHintsCSS();
785+
const expectedHistoryLength = (await _livePreviewCodeHintsCSS()).historyLength;
746786
let editor = EditorManager.getActiveEditor();
747787

748788
// now dismiss with escape
@@ -761,7 +801,7 @@ define(function (require, exports, module) {
761801
}, 30000);
762802

763803
it("should Live preview push css code hints selection changes to browser and commit(linked css)", async function () {
764-
const expectedHistoryLength = await _livePreviewCodeHintsCSS();
804+
const {historyLength: expectedHistoryLength, lastColor} = await _livePreviewCodeHintsCSS();
765805
let editor = EditorManager.getActiveEditor();
766806

767807
// now dismiss with escape
@@ -772,11 +812,8 @@ define(function (require, exports, module) {
772812
await awaitsFor(function () {
773813
return editor.getSelectedText() === "";
774814
}, "to restore the text to old state");
775-
// check if we have the new value
776-
if(!["antiquewhite", "aliceblue"].includes(editor.getToken().string)){
777-
// so depends on the bug in _livePreviewCodeHintsCSS which color is at present.
778-
expect("color should have beein either aliceblue or antiquewhite").toBeTrue();
779-
}
815+
// the color previewed last is the one committed
816+
expect(editor.getToken().string).toBe(lastColor);
780817

781818
// the undo history should be just one above
782819
expect(editor.getHistory().done.length).toBe(expectedHistoryLength + 3);
@@ -1298,15 +1335,18 @@ define(function (require, exports, module) {
12981335
}, 30000);
12991336

13001337
async function forRemoteExec(script, compareFn) {
1301-
let result;
1338+
let result, replied = false;
13021339
await awaitsFor(
13031340
function () {
13041341
LiveDevProtocol.evaluate(script)
13051342
.done((response) => {
1306-
result = JSON.parse(response.result || "");
1343+
// An undefined value comes back with no result at all.
1344+
result = response.result ? JSON.parse(response.result) : undefined;
1345+
replied = true;
13071346
});
13081347
if (compareFn) {
1309-
return compareFn(result);
1348+
// The reply is async: until the first one arrives there is nothing to compare.
1349+
return replied && compareFn(result);
13101350
}
13111351
// just exec and return if no compare function is specified
13121352
return true;
@@ -1388,17 +1428,17 @@ define(function (require, exports, module) {
13881428
it("should live highlight resize as window size changes", async function () {
13891429
await awaitsForDone(SpecRunnerUtils.openProjectFiles(["simple1.html"]),
13901430
"SpecRunnerUtils.openProjectFiles simple1.html");
1391-
let iFrame = testWindow.document.getElementById("panel-live-preview-frame");
13921431

13931432
await waitsForLiveDevelopmentToOpen();
1433+
// Looked up after the preview opened: each page load gets a fresh iframe.
1434+
let iFrame = testWindow.document.getElementById("panel-live-preview-frame");
13941435
let editor = EditorManager.getActiveEditor();
13951436
await forRemoteExec(`_LD.getHighlightCount()`, (result) => {
13961437
return result === 0;
13971438
});
13981439

13991440
editor.setCursorPos({ line: 11, ch: 10 });
14001441

1401-
await awaits(500);
14021442
await forRemoteExec(`_LD.getHighlightCount()`, (result) => {
14031443
return result === 1;
14041444
});
@@ -1409,12 +1449,10 @@ define(function (require, exports, module) {
14091449
});
14101450

14111451
iFrame.style.width = "100px";
1412-
await awaits(500);
14131452
await forRemoteExec(`_LD.getHighlightStyle(0, 'width')`, (result) => {
14141453
return originalWidth !== result;
14151454
});
14161455
iFrame.style.width = "100%";
1417-
await awaits(500);
14181456
await forRemoteExec(`_LD.getHighlightStyle(0, 'width')`, (result) => {
14191457
return originalWidth === result;
14201458
});
@@ -1834,34 +1872,31 @@ define(function (require, exports, module) {
18341872
"SpecRunnerUtils.openProjectFiles simple1.html");
18351873

18361874
await waitsForLiveDevelopmentToOpen();
1837-
let iFrame = testWindow.document.getElementById("panel-live-preview-frame");
1838-
expect(iFrame.src.endsWith(`simple1.html`))
1839-
.toBeTrue();
1875+
await _waitForIframeSrc("simple1.html");
18401876

18411877
let pinURLBtn = testWindow.$(testWindow.document.getElementById("pinURLButton"));
18421878
pinURLBtn.click();
18431879

18441880
await awaitsForDone(SpecRunnerUtils.openProjectFiles(["simple2.html"]),
18451881
"simple2.html");
1846-
await awaits(500);
1847-
expect(iFrame.src.endsWith(`simple1.html`))
1848-
.toBeTrue();
1882+
// Pinned: the preview stays on simple1.
1883+
await _waitForIframeSrc("simple1.html");
18491884

1850-
// now close the live preview panel by clicking on live preview extension icon
1885+
// now hide and show the live preview panel by clicking on live preview extension icon
18511886
let livePreviewBtn = testWindow.$(testWindow.document.getElementById("toolbar-go-live"));
18521887
livePreviewBtn.click();
1853-
await awaits(500);
1888+
await awaitsFor(() => !WorkspaceManager.isPanelVisible("live-preview-panel"),
1889+
"live preview panel to hide");
18541890
livePreviewBtn.click();
1855-
await awaits(500);
1856-
expect(iFrame.src.endsWith(`simple1.html`))
1857-
.toBeTrue();
1891+
await awaitsFor(() => WorkspaceManager.isPanelVisible("live-preview-panel"),
1892+
"live preview panel to show");
1893+
_reopenLiveDevIfStopped();
1894+
// Still pinned: the panel shows simple1 again.
1895+
await _waitForIframeSrc("simple1.html");
18581896

1897+
// Unpinned: the preview follows the current file, simple2.
18591898
pinURLBtn.click();
1860-
1861-
await awaits(1000);
1862-
let outerIFrame = testWindow.document.getElementById("panel-live-preview-frame");
1863-
let srcURL = new URL(outerIFrame.src);
1864-
expect(srcURL.pathname.endsWith("simple2.html")).toBeTrue();
1899+
await _waitForIframeSrc("simple2.html");
18651900

18661901
await endPreviewSession();
18671902
}, 30000);

‎test/spec/md-editor-integ-test.js‎

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -576,6 +576,17 @@ define(function (require, exports, module) {
576576
await SpecRunnerUtils.deletePathAsync(mdTestFolder + "/.phcode.json", true);
577577
}
578578

579+
/**
580+
* Hiding the panel closes Live Preview when no preview client is connected, and in a
581+
* test window showing the panel again does not reopen it: tests start Live Preview
582+
* themselves. Reopen it the way the app does, so the preview server comes back.
583+
*/
584+
function _reopenLiveDevIfStopped() {
585+
if (LiveDevMultiBrowser.status === LiveDevMultiBrowser.STATUS_INACTIVE) {
586+
LiveDevMultiBrowser.open();
587+
}
588+
}
589+
579590
async function _openMdFileAndWaitForPreview(fileName) {
580591
await awaitsForDone(SpecRunnerUtils.openProjectFiles([fileName]),
581592
"open " + fileName);
@@ -635,11 +646,21 @@ define(function (require, exports, module) {
635646
}, 30000);
636647

637648
beforeEach(async function () {
649+
// A test that hid the panel can leave Live Preview stopped. Start it again, so one
650+
// such test cannot take every later preview in this group down with it.
651+
if (LiveDevMultiBrowser.status === LiveDevMultiBrowser.STATUS_INACTIVE) {
652+
await awaitsForDone(SpecRunnerUtils.openProjectFiles(["simple.html"]),
653+
"open simple.html to restart live dev");
654+
LiveDevMultiBrowser.open();
655+
await awaitsFor(() => {
656+
return LiveDevMultiBrowser.status === LiveDevMultiBrowser.STATUS_ACTIVE;
657+
}, "live dev to restart", 20000);
658+
}
638659
// Reset scroll and close files between tests to prevent state leakage
639660
_setViewerScrollTop(0);
640661
await awaitsForDone(CommandManager.execute(Commands.FILE_CLOSE_ALL, { _forceClose: true }),
641662
"close all between cache tests");
642-
}, 10000);
663+
}, 30000);
643664

644665
it("should switch between MD files with viewer showing correct content", async function () {
645666
await _openMdFileAndWaitForPreview("doc1.md");
@@ -766,6 +787,7 @@ define(function (require, exports, module) {
766787
await awaitsForDone(CommandManager.execute(Commands.FILE_LIVE_FILE_PREVIEW));
767788
await awaitsFor(() => WorkspaceManager.isPanelVisible("live-preview-panel"),
768789
"live preview panel to reopen");
790+
_reopenLiveDevIfStopped();
769791
await _waitForMdPreviewReady(EditorManager.getActiveEditor());
770792

771793
// Verify iframe persisted (JS variable survived)

0 commit comments

Comments
 (0)