cypress icon indicating copy to clipboard operation
cypress copied to clipboard

Verification of baseUrl is failing when using `hosts` in cypress.json

Open dani8art opened this issue 2 years ago • 5 comments

Current behavior

Having a cypress.json like following

{
  "baseUrl": "http://wordpress.local",
  "hosts": {
    "wordpress.local": "121.0.0.1"
  }
}

When running cypress we get an unexpected behavior, Cypress fails to verify the URL.

DEBUG=cypress:network:connect,cypress:server:server-e2e,cypress:server:server-base cypress run -P /tmp/wordpress/cypress
[30:0316/162352.034421:ERROR:bus.cc(392)] Failed to connect to the bus: Failed to connect to socket /run/dbus/system_bus_socket: No such file or directory
[30:0316/162352.036782:ERROR:bus.cc(392)] Failed to connect to the bus: Address does not contain a colon
[30:0316/162352.036984:ERROR:bus.cc(392)] Failed to connect to the bus: Address does not contain a colon
[202:0316/162352.061512:ERROR:gpu_init.cc(453)] Passthrough is not supported, GL is swiftshader, ANGLE is
  cypress:server:server-base server open +0ms
  cypress:server:server-e2e createServer connecting to server +0ms
  cypress:server:server-base Server listening on  { address: '127.0.0.1', family: 'IPv4', port: 36635 } +22ms
  cypress:network:connect beginning getAddress { hostname: 'wordpress.local', port: 80 } +0ms
  cypress:network:connect got addresses { hostname: 'wordpress.local', port: 80, addresses: '192.168.1.107' } +2ms
Cypress could not verify that this server is running:

  > http://wordpress.local/

We are verifying this server because it has been configured as your baseUrl.

Cypress automatically waits until your server is accessible before running tests.

We will try connecting to it 3 more times...
  cypress:network:connect beginning getAddress { hostname: 'wordpress.local', port: 80 } +3s
  cypress:network:connect got addresses { hostname: 'wordpress.local', port: 80, addresses: '192.168.1.107' } +1ms
We will try connecting to it 2 more times...
  cypress:network:connect beginning getAddress { hostname: 'wordpress.local', port: 80 } +3s
  cypress:network:connect got addresses { hostname: 'wordpress.local', port: 80, addresses: '192.168.1.107' } +1ms
We will try connecting to it 1 more time...

  cypress:network:connect beginning getAddress { hostname: 'wordpress.local', port: 80 } +4s
  cypress:network:connect got addresses { hostname: 'wordpress.local', port: 80, addresses: '192.168.1.107' } +2ms
  cypress:server:server-e2e
  cypress:server:server-e2e AggregateError of:
  cypress:server:server-e2e     Error: connect ECONNREFUSED 127.0.0.1:80
  cypress:server:server-e2e
  cypress:server:server-e2e     at SomePromiseArray._checkOutcome (/root/.cache/Cypress/9.5.2/Cypress/resources/app/node_modules/bluebird/js/release/some.js:82:17)
  cypress:server:server-e2e     at SomePromiseArray._promiseRejected (/root/.cache/Cypress/9.5.2/Cypress/resources/app/node_modules/bluebird/js/release/some.js:69:17)
  cypress:server:server-e2e     at Promise._settlePromise (/root/.cache/Cypress/9.5.2/Cypress/resources/app/node_modules/bluebird/js/release/promise.js:576:26)
  cypress:server:server-e2e     at Promise._settlePromise0 (/root/.cache/Cypress/9.5.2/Cypress/resources/app/node_modules/bluebird/js/release/promise.js:614:10)
  cypress:server:server-e2e     at Promise._settlePromises (/root/.cache/Cypress/9.5.2/Cypress/resources/app/node_modules/bluebird/js/release/promise.js:690:18)
  cypress:server:server-e2e     at _drainQueueStep (/root/.cache/Cypress/9.5.2/Cypress/resources/app/node_modules/bluebird/js/release/async.js:138:12)
  cypress:server:server-e2e     at _drainQueue (/root/.cache/Cypress/9.5.2/Cypress/resources/app/node_modules/bluebird/js/release/async.js:131:9)
  cypress:server:server-e2e     at Async._drainQueues (/root/.cache/Cypress/9.5.2/Cypress/resources/app/node_modules/bluebird/js/release/async.js:147:5)
  cypress:server:server-e2e     at Immediate.Async.drainQueues [as _onImmediate] (/root/.cache/Cypress/9.5.2/Cypress/resources/app/node_modules/bluebird/js/release/async.js:17:14)
  cypress:server:server-e2e     at processImmediate (node:internal/timers:464:21)
  cypress:server:server-e2e  +10s
  cypress:server:server-base Setting remoteAuth undefined +10s
  cypress:server:server-base Setting remoteOrigin http://wordpress.local +2ms
  cypress:server:server-base Setting remoteHostAndPort { port: '80', tld: 'local', domain: 'wordpress' } +0ms
  cypress:server:server-base Setting remoteDocDomain wordpress.local +0ms
  cypress:server:server-base Getting remote state: { auth: undefined, props: { port: '80', tld: 'local', domain: 'wordpress' }, origin: 'http://wordpress.local', strategy: 'http', visiting: undefined, domainName: 'wordpress.local', fileServer: null } +1ms
Cypress failed to verify that your server is running.

However if we remove baseUrl from the cypress.json and we use cy.visit('http://wordpress.local/'); instead of cy.visit('/'); it work perfect.

{
  "hosts": {
    "wordpress.local": "121.0.0.1"
  }
}
  cypress:server:server-base server open +0ms
  cypress:server:server-e2e createServer connecting to server +0ms
  cypress:server:server-base Server listening on  { address: '127.0.0.1', family: 'IPv4', port: 32997 } +19ms
  cypress:server:server-base Setting remoteAuth undefined +36ms
  cypress:server:server-base Setting remoteOrigin http://localhost:32997 +1ms
  cypress:server:server-base Setting remoteStrategy file +0ms
  cypress:server:server-base Setting remoteHostAndPort null +0ms
  cypress:server:server-base Setting remoteDocDomain localhost +0ms
  cypress:server:server-base Setting remoteFileServer http://localhost:32783 +0ms
  cypress:server:server-base Getting remote state: { auth: undefined, props: null, origin: 'http://localhost:32997', strategy: 'file', visiting: undefined, domainName: 'localhost', fileServer: 'http://localhost:32783' } +1ms
====================================================================================================

  (Run Starting)

  ┌────────────────────────────────────────────────────────────────────────────────────────────────┐
  │ Cypress:        9.4.1                                                                          │
  │ Browser:        Electron 94 (headless)                                                         │
  │ Node Version:   v14.19.0 (/usr/bin/node)                                                       │
  │ Specs:          1 found (wordpress_spec.js)                                                    │
  └────────────────────────────────────────────────────────────────────────────────────────────────┘
...
  cypress:server:server-base Getting remote state: { auth: null, props: { port: '80', tld: 'local', domain: 'wordpress' }, origin: 'http://wordpress.local', strategy: 'http', visiting: false, domainName: 'wordpress.local', fileServer: null } +1ms
  cypress:network:connect beginning getAddress { hostname: 'wordpress.local', port: 80 } +464ms
  cypress:network:connect got addresses { hostname: 'wordpress.local', port: 80, addresses: '192.168.1.107' } +1ms

Desired behavior

We are using a Linux base container like cypress/included:9.4.1 in our CI system so the error happens. However, it doesn't happen locally using MacOS. You can see the following logs

$ DEBUG=cypress:network:connect,cypress:server:server-e2e,cypress:server:server-base cypress run -P /examples/wordpress/cypress
  cypress:server:server-base server open +0ms
  cypress:server:server-e2e createServer connecting to server +0ms
  cypress:server:server-base Server listening on  { address: '127.0.0.1', family: 'IPv4', port: 49206 } +11ms
  cypress:network:connect beginning getAddress { hostname: 'wordpress.local', port: 80 } +0ms
  cypress:network:connect got addresses { hostname: 'wordpress.local', port: 80, addresses: '127.0.0.1' } +1ms
  cypress:server:server-base Setting remoteAuth undefined +23ms
  cypress:server:server-base Setting remoteOrigin http://wordpress.local +0ms
  cypress:server:server-base Setting remoteHostAndPort { port: '80', tld: 'local', domain: 'wordpress' } +0ms
  cypress:server:server-base Setting remoteDocDomain wordpress.local +1ms
  cypress:server:server-base Getting remote state: { auth: undefined, props: { port: '80', tld: 'local', domain: 'wordpress' }, origin: 'http://wordpress.local', strategy: 'http', visiting: undefined, domainName: 'wordpress.local', fileServer: null } +0ms
...
  (Run Starting)

  ┌────────────────────────────────────────────────────────────────────────────────────────────────┐
  │ Cypress:        9.4.1                                                                          │
  │ Browser:        Electron 94 (headless)                                                         │
  │ Node Version:   v14.19.0 (/Users/darteaga/.nvm/versions/node/v14.19.0/bin/node)                │
  │ Specs:          1 found (wordpress_spec.js)                                                    │
  └────────────────────────────────────────────────────────────────────────────────────────────────┘


───────────────────

The desired behavior is this working on a Linux system. The problem seems to be related to the verification of the baseUrl that is performed when it is found in the cypress.jso.

Test code to reproduce

Cypress.json

{
  "baseUrl": "https://google.local"
  "hosts": {
    "google.local": "216.58.214.142"
  }
}

spec.js

/// <reference types="cypress" />

it('Google is shown', () => {
    cy.visit('/');
    cy.get('img.lnXdpd').should('have.attr', 'src').should('include','googlelogo_color_272x92dp.png');
});

Cypress Version

9.4.1

Other

$ cypress --version
Cypress package version: 9.4.1
Cypress binary version: 9.4.1
Electron version: 15.3.4
Bundled Node version: 16.5.0

dani8art avatar Mar 16 '22 17:03 dani8art

Hi 👋🏼 @jennifer-shehane, @chrisbreiding, can anyone take a look at this issue? This hosts feature is critical for us when testing complex authentication workflows like OAuth.

We have developed a workaround to bypass the initial checks but it would be great to get this issue fixed.

workaround

cypress.json

{
    "hosts": {
      "my.ip": "127.0.0.1"
    },
    "env": {
      "baseUrl": "https://my.ip"
    }
}

support/index.js

// TODO: remove this WORKAROUND for https://github.com/cypress-io/cypress/issues/20647,
//   then you should use baseUrl instead of env.baseUrl
Cypress.Commands.overwrite('visit', (original, ...args) => {
  if (!Cypress.env("baseUrl")) {
    throw new Error("Not found $.env.baseUrl but it is required");
  }

  const relative = args.shift();
  const target = new URL(relative, new URL(Cypress.env("baseUrl")));

  return new Promise((resolve) => {
    resolve(original(target.toString(), ...args));
  });
});

Thank you so much in advance.

dani8art avatar Jun 13 '22 07:06 dani8art

Hi! We have encountered the same problem but in our case, we cannot use the workaround proposed by @dani8art.

We need hosts and baseUrl configs working at the same time because we need to map the domain to an IP and, additionally, we need to avoid the "reloading error" in the middle of a test whose solution is to define the baseUrl ( tickets: https://github.com/cypress-io/cypress/issues/2636, https://github.com/cypress-io/cypress/issues/3454, https://github.com/cypress-io/cypress/issues/2777 ... ).

So this is also critical for us and we can not use the workaround.

ManuelLR avatar Jun 14 '22 10:06 ManuelLR

This is apparently a side-effect of how #383 was originally implemented: https://github.com/cypress-io/cypress/blame/547b700331c251410a1d17872d2a19c2009dec42/packages/server/lib/server-e2e.ts#L103-L125

Perhaps instead of just if (baseUrl) this should be something like if (baseUrl && configWaitForBaseUrl) (hypothetical config var)

If/when #22676 is implemented, this should be kept in mind as well.

funktionalsystems avatar Nov 22 '22 22:11 funktionalsystems

If/when https://github.com/cypress-io/cypress/issues/22676 is implemented, this should be kept in mind as well.

@funktionalsystems you are right, we should keep it

However, this case is a bit special because we want the check to be performed. The issue here is that the $.hosts property is not taken into account to do this check.

Additionally, it only happens for Linux environments, so it shouldn't be because of the piece of code you attached.

dani8art avatar Nov 23 '22 10:11 dani8art

@dani8art I agree that fixing hosts resolution is a much better solution that disabling the check. For now, I've reformulated your workaround above for the new v10+ config system. It works for me, and I wonder if it would also solve the issues @ManuelLR was having?

cypress/support/e2e.js:

beforeEach(() => {
  // There is currently a bug preventing `hosts` config from working when `baseUrl` is set:
  // https://github.com/cypress-io/cypress/issues/20647
  // Work around this by specifying `baseUrl` via an innocent env var, and then (if it has a value) inject that.
  // Do this in `beforeEach` because https://docs.cypress.io/api/cypress-api/config says
  // "Configuration set using Cypress.config is only in scope for the current spec file."
  let x = Cypress.env("CYPRESSTEST_URL");
  if(x) {
    Cypress.config("baseUrl", x);
  }
})

funktionalsystems avatar Nov 26 '22 20:11 funktionalsystems

Additionally, it only happens for Linux environments, so it shouldn't be because of the piece of code you attached.

@dani8art I am also on Linux (only) - I don't have a good way to test other platforms, but very odd if it doesn't happen elsewhere.

It seems like it HAS to be that chunk of code - That's the only place that emits CANNOT_CONNECT_BASE_URL ("Cypress could not verify that this server is running")

DNS overrides appear to be correctly checked by getAddress in packages/network/lib/connect.ts (last touched by !3531) Call chain:

	packages/server/lib/util/ensure-url.ts
	retryIsListening(
		isListening(
			connect.getAddress(Number(port), String(hostname)) 
				packages/network/lib/connect.ts
					// promisify at the very last second which enables us to
					// modify dns lookup function (via hosts overrides)
					const lookupAsync = Bluebird.promisify<LookupAddress[], string, LookupAllOptions>(dns.lookup, { context: dns })

And DNS overrides appear to be correctly set by createHosts in packages/server/lib/server-base.ts Call chain: server-base.open -> createHosts -> evilDns.add

And in general the DNS overrides appear to work correctly later on in the tests, as long as we don't get stopped by the baseUrl bug.

So there must be some kind of runtime scope/timing issue where it's not getting set before being used, even though it looks "ok" lexically. I'm no JS/TS expert, but is this some async/await/promises/Bluebird shenanigans? Call chain from packages/server/lib/project-base.ts:

	async open () {
		debug('opening project instance %s'
		this.createServer -> ServerE2E#constructor() -> ServerBase#constructor()
		await this._server.open(
			server-e2e.open -> server-base.open 
			server-base.open
				debug('server open')
				-> createHosts -> evilDns.add  (TODO: a debug log here might help verify timing!)
				-> (abstract)this.createServer
				server-e2e.createServer
					new Bluebird
						debug('createServer connecting to server')
						this.server -> server-base.ensureProp cypress/packages/server/lib/util/class-helpers.ts
							(If this were a problem, it would throw Error `ServerE2E#open must first be called before accessing 'this.server'`)
						this._listen().then()
							Bluebird.all([]).spread()
								ensureUrl.isListening(baseUrl)
		debug('project config: 

Again, I'm not expert, but that last part (last touched by !14479) looks especially suspect to me.

funktionalsystems avatar Nov 28 '22 19:11 funktionalsystems

Actually, it looks like dns is resolving hosts ok in all cases. - Perhaps this is somehow a bug in isListening (ensure-url.ts)

Here's (sanitized) what I get when running with --config baseUrl=https://my.redacted.example.com,hosts={\"*.redacted.example.com\":\"$MY_REDACTED_IP\"}

  cypress:server:server-base server open +0ms
  cypress:server:server-e2e createServer connecting to server +0ms
  cypress:server:server-base Server listening on  { address: '127.0.0.1', family: 'IPv4', port: 46389 } +31ms
  cypress:network:connect beginning getAddress { hostname: 'my.redacted.example.com', port: 443 } +0ms
  cypress:network:connect got addresses { hostname: 'my.redacted.example.com', port: 443, addresses: '[MASKED]' } +1ms
Cypress could not verify that this server is running:
  > https://my.redacted.example.com
We are verifying this server because it has been configured as your baseUrl.
Cypress automatically waits until your server is accessible before running tests.
We will try connecting to it 3 more times...
  cypress:network:connect beginning getAddress { hostname: 'my.redacted.example.com', port: 443 } +3s
  cypress:network:connect got addresses { hostname: 'my.redacted.example.com', port: 443, addresses: '[MASKED]' } +0ms
We will try connecting to it 2 more times...
  cypress:network:connect beginning getAddress { hostname: 'my.redacted.example.com', port: 443 } +3s
  cypress:network:connect got addresses { hostname: 'my.redacted.example.com', port: 443, addresses: '[MASKED]' } +1ms
We will try connecting to it 1 more time...
  cypress:network:connect beginning getAddress { hostname: 'my.redacted.example.com', port: 443 } +4s
  cypress:network:connect got addresses { hostname: 'my.redacted.example.com', port: 443, addresses: '[MASKED]' } +1ms
  cypress:server:server-e2e AggregateError: aggregate error
  cypress:server:server-e2e     at SomePromiseArray._checkOutcome (/.cache/Cypress/11.1.0/Cypress/resources/app/node_modules/bluebird/js/release/some.js:82:17)
  cypress:server:server-e2e     at SomePromiseArray._promiseRejected (/.cache/Cypress/11.1.0/Cypress/resources/app/node_modules/bluebird/js/release/some.js:69:17)
  cypress:server:server-e2e     at Promise._settlePromise (/.cache/Cypress/11.1.0/Cypress/resources/app/node_modules/bluebird/js/release/promise.js:576:26)
  cypress:server:server-e2e     at Promise._settlePromise0 (/.cache/Cypress/11.1.0/Cypress/resources/app/node_modules/bluebird/js/release/promise.js:614:10)
  cypress:server:server-e2e     at Promise._settlePromises (/.cache/Cypress/11.1.0/Cypress/resources/app/node_modules/bluebird/js/release/promise.js:690:18)
  cypress:server:server-e2e     at _drainQueueStep (/.cache/Cypress/11.1.0/Cypress/resources/app/node_modules/bluebird/js/release/async.js:138:12)
  cypress:server:server-e2e     at _drainQueue (/.cache/Cypress/11.1.0/Cypress/resources/app/node_modules/bluebird/js/release/async.js:131:9)
  cypress:server:server-e2e     at Async._drainQueues (/.cache/Cypress/11.1.0/Cypress/resources/app/node_modules/bluebird/js/release/async.js:147:5)
  cypress:server:server-e2e     at Async.drainQueues [as _onImmediate] (/.cache/Cypress/11.1.0/Cypress/resources/app/node_modules/bluebird/js/release/async.js:17:14)
  cypress:server:server-e2e     at process.processImmediate (node:internal/timers:466:21) +10s
Cypress failed to verify that your server is running.
Please start this server and then run Cypress again.

The [MASKED] is my correct IP. So somehow it is resolving yet still "failing." If I use my previously mentioned workaround and run with --env "CYPRESSTEST_URL=https://my.redacted.example.com" --config hosts={\"*.redacted.example.com\":\"$MY_REDACTED_IP\"} it skips the check and works fine.

funktionalsystems avatar Nov 29 '22 00:11 funktionalsystems

Wow, @funktionalsystems this is really great research, Since I saw that it was working depending on the OS I thought it might be related to "be some kind of runtime scope/timing issue".

However, I can see this line

cypress:server:server-base Server listening on  { address: '127.0.0.1', family: 'IPv4', port: 46389 }

Which is always pointing to 127.0.0.1 instead of being pointing to $MY_REDACTED_IP as you have mentioned it may be related to "Perhaps this is somehow a bug in isListening (ensure-url.ts)"

dani8art avatar Dec 01 '22 10:12 dani8art

This issue has not had any activity in 180 days. Cypress evolves quickly and the reported behavior should be tested on the latest version of Cypress to verify the behavior is still occurring. It will be closed in 14 days if no updates are provided.

cypress-app-bot avatar May 31 '23 20:05 cypress-app-bot

Do not close, issue still exists.

henryruhs avatar Jun 12 '23 10:06 henryruhs

Got to love when bots mark issues as stale. "We haven't fixed this, I know, hide it!"

mryellow avatar Jun 12 '23 22:06 mryellow

Hi everyone, unfortunately, the hosts config option is not officially supported and is not listed in our configuration documentation. As such, I am going to close this issue. However, please feel free to continue any discussions. If someone would like to make an official feature request for the hosts option, please create a feature request here.

mschile avatar Jun 13 '23 15:06 mschile