Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 0 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion src/configuration/testcafe-configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -330,10 +330,12 @@ export default class TestCafeConfiguration extends Configuration {
this.mergeOptions({ hostname });
}

public async calculateHostname ({ nativeAutomation } = { nativeAutomation: false }): Promise<void> {
public async calculateHostname ({ nativeAutomation, allBrowsersLocal } = { nativeAutomation: false, allBrowsersLocal: false }): Promise<void> {
await this.ensureHostname(async hostname => {
if (nativeAutomation)
hostname = LOCALHOST_NAMES.LOCALHOST;
else if (!hostname && allBrowsersLocal)
hostname = LOCALHOST_NAMES.LOCALHOST;
else
hostname = await getValidHostname(hostname);

Expand Down
17 changes: 13 additions & 4 deletions src/runner/bootstrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,11 +171,16 @@ export default class Bootstrapper {
};
}

private async _setupProxy (): Promise<void> {
private async _setupProxy (browserInfo: BrowserInfoSource[]): Promise<void> {
if (this.browserConnectionGateway.status === BrowserConnectionGatewayStatus.initialized)
return;

await this.configuration.calculateHostname({ nativeAutomation: !this.configuration.getOption(OPTION_NAMES.disableNativeAutomation) });
const allBrowsersLocal = await this._isAllBrowsersLocal(browserInfo);

await this.configuration.calculateHostname({
nativeAutomation: !this.configuration.getOption(OPTION_NAMES.disableNativeAutomation),
allBrowsersLocal,
});

this.browserConnectionGateway.initialize(this.configuration.startOptions);
}
Expand Down Expand Up @@ -209,7 +214,7 @@ export default class Bootstrapper {

this._validateUserProfileOptionInNativeAutomation(automated);

await this._setupProxy();
await this._setupProxy(browserInfo);

let browserConnections = this._createAutomatedConnections(automated);

Expand Down Expand Up @@ -349,13 +354,17 @@ export default class Bootstrapper {
return testedApp;
}

private async _canUseParallelBootstrapping (browserInfo: BrowserInfoSource[]): Promise<boolean> {
private async _isAllBrowsersLocal (browserInfo: BrowserInfoSource[]): Promise<boolean> {
const isLocalPromises = browserInfo.map(browser => browser.provider.isLocalBrowser(void 0, Bootstrapper._getBrowserName(browser)));
const isLocalBrowsers = await Promise.all(isLocalPromises);

return isLocalBrowsers.every(result => result);
}
Comment on lines +357 to 362
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_isAllBrowsersLocal calls browser.provider.isLocalBrowser(void 0, ...) for every BrowserInfoSource. When the entry is a BrowserConnection, an id is available (browser.id) and some providers rely on it to determine locality; passing undefined risks misclassifying remote connections as local, which can incorrectly force hostname to localhost in proxy mode. Pass the connection id for BrowserConnection instances (and keep undefined for plain BrowserInfo).

Copilot uses AI. Check for mistakes.

private async _canUseParallelBootstrapping (browserInfo: BrowserInfoSource[]): Promise<boolean> {
return this._isAllBrowsersLocal(browserInfo);
}

private async _bootstrapSequence (browserInfo: BrowserInfoSource[], id: string): Promise<BasicRuntimeResources> {
const tests = await this._getTests(id);
const testedApp = await this._startTestedApp();
Expand Down
10 changes: 10 additions & 0 deletions test/functional/fixtures/regression/gh-8391/pages/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>GH-8391</title>
</head>
<body>
<div id="status">ok</div>
</body>
</html>
5 changes: 5 additions & 0 deletions test/functional/fixtures/regression/gh-8391/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
describe('[Regression](GH-8391)', function () {
it('Should not fail Firefox proxy-mode initialization', function () {
return runTests('testcafe-fixtures/index.js', 'Should run a simple assertion in Firefox proxy mode', { only: 'firefox' });
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This regression test is intended to verify Firefox behavior specifically in proxy mode (--disable-native-automation), but it doesn’t enforce that mode. Since the functional test harness can run with NATIVE_AUTOMATION=true, this test may execute in native automation and fail to cover the reported scenario. Please skip/guard this test when config.nativeAutomation is true (or explicitly run with disableNativeAutomation: true in this test).

Suggested change
return runTests('testcafe-fixtures/index.js', 'Should run a simple assertion in Firefox proxy mode', { only: 'firefox' });
return runTests('testcafe-fixtures/index.js', 'Should run a simple assertion in Firefox proxy mode', { only: 'firefox', disableNativeAutomation: true });

Copilot uses AI. Check for mistakes.
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Selector } from 'testcafe';

fixture('GH-8391 - Firefox proxy mode should initialize scripts')
.page`http://localhost:3000/fixtures/regression/gh-8391/pages/index.html`;

test('Should run a simple assertion in Firefox proxy mode', async t => {
await t.expect(Selector('#status').innerText).eql('ok');
});
34 changes: 34 additions & 0 deletions test/server/bootstrapper-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -189,5 +189,39 @@ describe('Bootstrapper', () => {
'the "userProfile" suffix from the following browser aliases: "chrome, edge".');
}
});

it('Should use localhost hostname strategy for local browsers in proxy mode', async () => {
let calculateHostnameOptions = null;

const originalGetOption = bootstrapper.configuration.getOption;
const originalCalculateHostname = bootstrapper.configuration.calculateHostname;

try {
bootstrapper.configuration.getOption = optionName => {
if (optionName === 'disableNativeAutomation')
return true;

return originalGetOption(optionName);
};

bootstrapper.configuration.calculateHostname = options => {
calculateHostnameOptions = options;
};

await bootstrapper._setupProxy([{
browserName: 'firefox',
provider: createBrowserProviderMock({ local: true }),
}]);

expect(calculateHostnameOptions).eql({
nativeAutomation: false,
allBrowsersLocal: true,
});
}
finally {
bootstrapper.configuration.getOption = originalGetOption;
bootstrapper.configuration.calculateHostname = originalCalculateHostname;
}
});
});
});
14 changes: 14 additions & 0 deletions test/server/configuration-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,20 @@ describe('TestCafeConfiguration', function () {

expect(configuration.getOption('hostname')).eql('123.456.789');
});

it('Native automation is disabled/all browsers are local/hostname is unset', async () => {
await configuration.init();
await configuration.calculateHostname({ nativeAutomation: false, allBrowsersLocal: true });

expect(configuration.getOption('hostname')).eql('localhost');
});

it('Native automation is disabled/all browsers are local/hostname is set', async () => {
await configuration.init({ hostname: '123.456.789' });
await configuration.calculateHostname({ nativeAutomation: false, allBrowsersLocal: true });

expect(configuration.getOption('hostname')).eql('123.456.789');
});
});
});

Expand Down
Loading