Skip to content

Commit 6684347

Browse files
feat: test against scratch orgs
1 parent 4c4444c commit 6684347

7 files changed

Lines changed: 86 additions & 57 deletions

File tree

test/commands/lightning/dev/component-preview/browserMenu.nut.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,9 @@ describe('lightning preview menu', () => {
3535

3636
beforeEach(async () => {
3737
session = await getSession();
38-
childProcess = startLightningDevServer(
39-
session.project?.dir ?? '',
40-
session.hubOrg.username,
41-
{ AUTO_ENABLE_LOCAL_DEV: 'true' },
42-
COMPONENT_NAME,
43-
);
38+
childProcess = startLightningDevServer(session, { AUTO_ENABLE_LOCAL_DEV: 'true' }, COMPONENT_NAME);
4439
const previewUrl = await getPreviewURL(childProcess.stdout);
45-
({ browser, page } = await getPreview(previewUrl, session.hubOrg.accessToken));
40+
({ browser, page } = await getPreview(previewUrl, session));
4641
});
4742

4843
afterEach(async () => {

test/commands/lightning/dev/component-preview/componentError.nut.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,9 @@ describe('lightning preview component error', () => {
3737

3838
before(async () => {
3939
session = await getSession();
40-
childProcess = startLightningDevServer(
41-
session.project?.dir ?? '',
42-
session.hubOrg.username,
43-
{ AUTO_ENABLE_LOCAL_DEV: 'true' },
44-
COMPONENT_NAME,
45-
);
40+
childProcess = startLightningDevServer(session, { AUTO_ENABLE_LOCAL_DEV: 'true' }, COMPONENT_NAME);
4641
const previewUrl = await getPreviewURL(childProcess.stdout);
47-
({ browser, page } = await getPreview(previewUrl, session.hubOrg.accessToken));
42+
({ browser, page } = await getPreview(previewUrl, session));
4843
});
4944

5045
after(async () => {

test/commands/lightning/dev/component-preview/hmr.nut.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,9 @@ describe('lightning preview hot module reload', () => {
3939

4040
before(async () => {
4141
session = await getSession();
42-
childProcess = startLightningDevServer(
43-
session.project?.dir ?? '',
44-
session.hubOrg.username,
45-
{ AUTO_ENABLE_LOCAL_DEV: 'true' },
46-
COMPONENT_NAME,
47-
);
42+
childProcess = startLightningDevServer(session, { AUTO_ENABLE_LOCAL_DEV: 'true' }, COMPONENT_NAME);
4843
const previewUrl = await getPreviewURL(childProcess.stdout);
49-
({ browser, page } = await getPreview(previewUrl, session.hubOrg.accessToken));
44+
({ browser, page } = await getPreview(previewUrl, session));
5045
});
5146

5247
after(async () => {

test/commands/lightning/dev/component-preview/prompts.nut.ts

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ describe('lightning preview component prompts', () => {
4545

4646
beforeEach(async () => {
4747
session = await getSession();
48-
const org = await Org.create({ aliasOrUsername: session.hubOrg.username });
48+
const org = await Org.create({ aliasOrUsername: session.orgs.get('default')?.username });
4949
connection = org.getConnection();
5050
// Unset required org configuration to trigger prompt behavior
5151
await MetaUtils.setLightningPreviewEnabled(connection, false);
@@ -59,7 +59,7 @@ describe('lightning preview component prompts', () => {
5959
});
6060

6161
it('should error out when local dev is not enabled and AUTO_ENABLE_LOCAL_DEV is false', async () => {
62-
childProcess = startLightningDevServer(session.project?.dir, session.hubOrg.username, {
62+
childProcess = startLightningDevServer(session, {
6363
AUTO_ENABLE_LOCAL_DEV: false,
6464
});
6565

@@ -69,7 +69,7 @@ describe('lightning preview component prompts', () => {
6969
});
7070

7171
it('should error out when user answers "n" to enable local dev prompt', async () => {
72-
childProcess = startLightningDevServer(session.project?.dir, session.hubOrg.username);
72+
childProcess = startLightningDevServer(session);
7373

7474
// Wait for enable local dev prompt and answer Y to enable local dev
7575
await waitForPrompt(childProcess, promptMessages.getMessage('component.enable-local-dev'));
@@ -81,7 +81,7 @@ describe('lightning preview component prompts', () => {
8181
});
8282

8383
it('should enable local dev and disable first party cookies and render page after selecting component when user answers "Y" to enable local dev', async () => {
84-
childProcess = startLightningDevServer(session.project?.dir, session.hubOrg.username);
84+
childProcess = startLightningDevServer(session);
8585

8686
// Wait for enable local dev prompt and answer Y to enable local dev
8787
await waitForPrompt(childProcess, promptMessages.getMessage('component.enable-local-dev'));
@@ -92,23 +92,18 @@ describe('lightning preview component prompts', () => {
9292
childProcess.stdin?.write('\n');
9393

9494
const previewUrl = await getPreviewURL(childProcess.stdout);
95-
({ browser, page } = await getPreview(previewUrl, session.hubOrg.accessToken));
95+
({ browser, page } = await getPreview(previewUrl, session));
9696

9797
const greetingLocator = page.getByText('Hello World');
9898
await greetingLocator.waitFor({ state: 'visible' });
9999
expect(await greetingLocator.textContent()).to.equal('Hello World');
100100
});
101101

102102
it('should render without a prompt and disable first party cookies when AUTO_ENABLE_LOCAL_DEV=true', async () => {
103-
childProcess = startLightningDevServer(
104-
session.project?.dir,
105-
session.hubOrg.username,
106-
{ AUTO_ENABLE_LOCAL_DEV: 'true' },
107-
COMPONENT_NAME,
108-
);
103+
childProcess = startLightningDevServer(session, { AUTO_ENABLE_LOCAL_DEV: 'true' }, COMPONENT_NAME);
109104

110105
const previewUrl = await getPreviewURL(childProcess.stdout);
111-
({ browser, page } = await getPreview(previewUrl, session.hubOrg.accessToken));
106+
({ browser, page } = await getPreview(previewUrl, session));
112107

113108
const greetingLocator = page.getByText('Hello World');
114109
await greetingLocator.waitFor({ state: 'visible' });

test/commands/lightning/dev/helpers/browserUtils.ts

Lines changed: 61 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -14,37 +14,76 @@
1414
* limitations under the License.
1515
*/
1616

17+
import { spawnSync } from 'node:child_process';
1718
import { chromium, type Browser, type Page } from 'playwright';
19+
import { TestSession } from '@salesforce/cli-plugins-testkit';
1820

1921
/**
20-
* Launches a headless (or headed, if HEADED env is set) browser, navigates to the component preview URL,
21-
* and optionally sets the session cookie for authenticated preview.
22+
* Returns the access token for frontdoor sid authentication. Required for
23+
* Playwright testing.
24+
*
25+
* @param session - TestSession with a default scratch org.
26+
* @returns The session ID string.
27+
*/
28+
export function getAccessToken(session: TestSession): string {
29+
const scratchOrg = session.orgs.get('default');
30+
const username = scratchOrg?.username ?? '';
31+
const projectDir = session.project?.dir ?? '';
32+
if (!username || !projectDir) {
33+
throw new Error('Session has no default scratch org username or project dir');
34+
}
35+
const result = spawnSync('sf', ['org', 'display', 'user', '-o', username, '--json'], {
36+
cwd: projectDir,
37+
encoding: 'utf8',
38+
maxBuffer: 10 * 1024,
39+
});
40+
if (result.status !== 0) {
41+
throw new Error(`sf org display user failed: ${result.stderr ?? result.error?.message ?? 'unknown'}`);
42+
}
43+
let displayUser: { result?: { accessToken?: string } };
44+
try {
45+
displayUser = JSON.parse(result.stdout ?? '{}') as { result?: { accessToken?: string } };
46+
} catch {
47+
throw new Error('sf org display user did not return valid JSON');
48+
}
49+
const accessToken = displayUser.result?.accessToken ?? '';
50+
if (!accessToken) {
51+
throw new Error('sf org display user result missing accessToken');
52+
}
53+
return accessToken;
54+
}
55+
56+
/**
57+
* Establishes a browser session by opening the Salesforce front-door URL with the access token.
58+
* The server redirects and sets session cookies (sid, sid_Client, etc.), so the LWR preview app
59+
* accepts the session. No password or form fill required.
60+
*
61+
* @param page - Playwright page.
62+
* @param previewOrigin - Org origin (e.g. from new URL(previewUrl).origin).
63+
* @param accessToken - Org access token (sid).
64+
*/
65+
async function establishSessionViaFrontDoor(page: Page, previewOrigin: string, accessToken: string): Promise<void> {
66+
const frontDoorUrl = `${previewOrigin}/secur/frontdoor.jsp?sid=${accessToken}`;
67+
await page.goto(frontDoorUrl, { waitUntil: 'commit' });
68+
}
69+
70+
/**
71+
* Launches a headless (or headed, if HEADED env is set) browser, establishes an authenticated
72+
* session (front-door URL first; falls back to form login if needed), then navigates to the preview URL.
2273
*
2374
* @param previewUrl - Full URL of the LWC component preview (e.g. from getPreviewURL).
24-
* @param accessToken - Optional org access token; if set, adds a 'sid' cookie so the preview page is authenticated.
75+
* @param session - TestSession with default scratch org.
76+
* @param options - Optional. Use form login only (skip front-door) by setting useFormLogin: true.
2577
* @returns Promise resolving to the Playwright browser and page; caller must close them when done.
2678
*/
27-
export async function getPreview(
28-
previewUrl: string,
29-
accessToken: string | undefined,
30-
): Promise<{ browser: Browser; page: Page }> {
79+
export async function getPreview(previewUrl: string, session: TestSession): Promise<{ browser: Browser; page: Page }> {
80+
const accessToken = getAccessToken(session);
3181
const previewOrigin = new URL(previewUrl).origin;
32-
let browser: Browser | null = null;
33-
let page: Page | null = null;
3482
const headed = process.env.HEADED === 'true' || process.env.HEADED === '1';
35-
browser = await chromium.launch({ headless: !headed });
36-
page = await browser.newPage();
37-
if (accessToken) {
38-
await page.context().addCookies([
39-
{
40-
name: 'sid',
41-
value: accessToken,
42-
domain: new URL(previewOrigin).hostname,
43-
path: '/',
44-
expires: Math.floor(Date.now() / 1000) + 86_400,
45-
},
46-
]);
47-
}
83+
const browser = await chromium.launch({ headless: !headed });
84+
const page = await browser.newPage();
85+
86+
await establishSessionViaFrontDoor(page, previewOrigin, accessToken);
4887
await page.goto(previewUrl, { waitUntil: 'load' });
4988
return new Promise((r) => r({ browser, page }));
5089
}

test/commands/lightning/dev/helpers/devServerUtils.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import type { Writable } from 'node:stream';
1919
import { type ChildProcess } from 'node:child_process';
2020
import path from 'node:path';
2121
import { fileURLToPath } from 'node:url';
22+
import { TestSession } from '@salesforce/cli-plugins-testkit';
2223

2324
const CURRENT_FILE_PATH = fileURLToPath(import.meta.url);
2425
const CURRENT_DIR_PATH = path.dirname(CURRENT_FILE_PATH);
@@ -134,11 +135,14 @@ export function waitForProcessExit(
134135
* @returns The spawned child process with piped stdio; use getPreviewURL(stdout) to get the preview URL.
135136
*/
136137
export function startLightningDevServer(
137-
projectDir: string,
138-
username: string = '',
138+
session: TestSession,
139139
env = {},
140140
componentName?: string,
141141
): ChildProcessWithoutNullStreams {
142+
const scratchOrg = session.orgs.get('default');
143+
const username = scratchOrg?.username ?? '';
144+
const projectDir = session.project?.dir ?? '';
145+
142146
const runJs = path.join(PLUGIN_ROOT_PATH, 'bin', 'run.js');
143147
const spawnEnv = {
144148
...process.env,

test/commands/lightning/dev/helpers/sessionUtils.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ export async function getSession(): Promise<TestSession> {
3232
cachedSession = await TestSession.create({
3333
devhubAuthStrategy: 'AUTO',
3434
project: { sourceDir: PROJECT_PATH },
35+
scratchOrgs: [
36+
{
37+
config: 'config/project-scratch-def.json',
38+
setDefault: true,
39+
},
40+
],
3541
});
3642
}
3743
return new Promise((r) => r(cachedSession));

0 commit comments

Comments
 (0)