cypress 9.0.0 → 9.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,7 +8,7 @@ const is = require('check-more-types');
8
8
 
9
9
  const os = require('os');
10
10
 
11
- const url = require('url');
11
+ const Url = require('url');
12
12
 
13
13
  const path = require('path');
14
14
 
@@ -36,6 +36,7 @@ const fs = require('../fs');
36
36
  const util = require('../util');
37
37
 
38
38
  const defaultBaseUrl = 'https://download.cypress.io/';
39
+ const defaultMaxRedirects = 10;
39
40
 
40
41
  const getProxyForUrlWithNpmConfig = url => {
41
42
  return getProxyForUrl(url) || process.env.npm_config_https_proxy || process.env.npm_config_proxy || null;
@@ -76,7 +77,7 @@ const getCA = () => {
76
77
  };
77
78
 
78
79
  const prepend = urlPath => {
79
- const endpoint = url.resolve(getBaseUrl(), urlPath);
80
+ const endpoint = Url.resolve(getBaseUrl(), urlPath);
80
81
  const platform = os.platform();
81
82
  return `${endpoint}?platform=${platform}&arch=${arch()}`;
82
83
  };
@@ -183,8 +184,18 @@ const downloadFromUrl = ({
183
184
  url,
184
185
  downloadDestination,
185
186
  progress,
186
- ca
187
+ ca,
188
+ version,
189
+ redirectTTL = defaultMaxRedirects
187
190
  }) => {
191
+ if (redirectTTL <= 0) {
192
+ return Promise.reject(new Error(stripIndent`
193
+ Failed downloading the Cypress binary.
194
+ There were too many redirects. The default allowance is ${defaultMaxRedirects}.
195
+ Maybe you got stuck in a redirect loop?
196
+ `));
197
+ }
198
+
188
199
  return new Promise((resolve, reject) => {
189
200
  const proxy = getProxyForUrlWithNpmConfig(url);
190
201
  debug('Downloading package', {
@@ -192,35 +203,24 @@ const downloadFromUrl = ({
192
203
  proxy,
193
204
  downloadDestination
194
205
  });
195
- let redirectVersion;
196
- const reqOptions = {
197
- url,
198
- proxy,
199
-
200
- followRedirect(response) {
201
- const version = response.headers['x-version'];
202
- debug('redirect version:', version);
203
-
204
- if (version) {
205
- // set the version in options if we have one.
206
- // this insulates us from potential redirect
207
- // problems where version would be set to undefined.
208
- redirectVersion = version;
209
- } // yes redirect
210
-
211
-
212
- return true;
213
- }
214
-
215
- };
216
206
 
217
207
  if (ca) {
218
208
  debug('using custom CA details from npm config');
219
- reqOptions.agentOptions = {
220
- ca
221
- };
222
209
  }
223
210
 
211
+ const reqOptions = {
212
+ uri: url,
213
+ ...(proxy ? {
214
+ proxy
215
+ } : {}),
216
+ ...(ca ? {
217
+ agentOptions: {
218
+ ca
219
+ }
220
+ } : {}),
221
+ method: 'GET',
222
+ followRedirect: false
223
+ };
224
224
  const req = request(reqOptions); // closure
225
225
 
226
226
  let started = null;
@@ -248,18 +248,46 @@ const downloadFromUrl = ({
248
248
  // response headers
249
249
 
250
250
 
251
- started = new Date(); // if our status code does not start with 200
252
-
253
- if (!/^2/.test(response.statusCode)) {
251
+ started = new Date();
252
+
253
+ if (/^3/.test(response.statusCode)) {
254
+ const redirectVersion = response.headers['x-version'];
255
+ const redirectUrl = response.headers.location;
256
+ debug('redirect version:', redirectVersion);
257
+ debug('redirect url:', redirectUrl);
258
+ downloadFromUrl({
259
+ url: redirectUrl,
260
+ progress,
261
+ ca,
262
+ downloadDestination,
263
+ version: redirectVersion,
264
+ redirectTTL: redirectTTL - 1
265
+ }).then(resolve).catch(reject); // if our status code does not start with 200
266
+ } else if (!/^2/.test(response.statusCode)) {
254
267
  debug('response code %d', response.statusCode);
255
268
  const err = new Error(stripIndent`
256
269
  Failed downloading the Cypress binary.
257
270
  Response code: ${response.statusCode}
258
271
  Response message: ${response.statusMessage}
259
272
  `);
260
- reject(err);
273
+ reject(err); // status codes here are all 2xx
274
+ } else {
275
+ // We only enable this pipe connection when we know we've got a successful return
276
+ // and handle the completion with verify and resolve
277
+ // there was a possible race condition between end of request and close of writeStream
278
+ // that is made ordered with this Promise.all
279
+ Promise.all([new Promise(r => {
280
+ return response.pipe(fs.createWriteStream(downloadDestination).on('close', r));
281
+ }), new Promise(r => response.on('end', r))]).then(() => {
282
+ debug('downloading finished');
283
+ verifyDownloadedFile(downloadDestination, expectedSize, expectedChecksum).then(() => debug('verified')).then(() => resolve(version)).catch(reject);
284
+ });
261
285
  }
262
- }).on('error', reject).on('progress', state => {
286
+ }).on('error', e => {
287
+ if (e.code === 'ECONNRESET') return; // sometimes proxies give ECONNRESET but we don't care
288
+
289
+ reject(e);
290
+ }).on('progress', state => {
263
291
  // total time we've elapsed
264
292
  // starting on our first progress notification
265
293
  const elapsed = new Date() - started; // request-progress sends a value between 0 and 1
@@ -268,12 +296,6 @@ const downloadFromUrl = ({
268
296
  const eta = util.calculateEta(percentage, elapsed); // send up our percent and seconds remaining
269
297
 
270
298
  progress.onProgress(percentage, util.secsRemaining(eta));
271
- }) // save this download here
272
- .pipe(fs.createWriteStream(downloadDestination)).on('finish', () => {
273
- debug('downloading finished');
274
- verifyDownloadedFile(downloadDestination, expectedSize, expectedChecksum).then(() => {
275
- return resolve(redirectVersion);
276
- }, reject);
277
299
  });
278
300
  });
279
301
  };
@@ -288,7 +310,8 @@ const start = opts => {
288
310
  let {
289
311
  version,
290
312
  downloadDestination,
291
- progress
313
+ progress,
314
+ redirectTTL
292
315
  } = opts;
293
316
 
294
317
  if (!downloadDestination) {
@@ -316,7 +339,11 @@ const start = opts => {
316
339
  url,
317
340
  downloadDestination,
318
341
  progress,
319
- ca
342
+ ca,
343
+ version,
344
+ ...(redirectTTL ? {
345
+ redirectTTL
346
+ } : {})
320
347
  });
321
348
  }).catch(err => {
322
349
  return prettyDownloadErr(err, version);
@@ -64,6 +64,10 @@ const getNpmArgv = () => {
64
64
  const getVersionSpecifier = (startDir = path.resolve(__dirname, '../..')) => {
65
65
  const argv = getNpmArgv();
66
66
 
67
+ if ((process.env.npm_package_resolved || '').endsWith('cypress.tgz')) {
68
+ return process.env.npm_package_resolved;
69
+ }
70
+
67
71
  if (argv) {
68
72
  const tgz = _.find(argv, t => t.endsWith('cypress.tgz'));
69
73
 
@@ -104,7 +108,7 @@ const getVersionSpecifier = (startDir = path.resolve(__dirname, '../..')) => {
104
108
  });
105
109
  };
106
110
 
107
- const betaNpmUrlRe = /^\/beta\/npm\/(?<version>[0-9.]+)\/(?<artifactSlug>[^/]+)\/cypress\.tgz$/; // convert a prerelease NPM package .tgz URL to the corresponding binary .zip URL
111
+ const betaNpmUrlRe = /^\/beta\/npm\/(?<version>[0-9.]+)\/(?<artifactSlug>.+?)\/cypress\.tgz$/; // convert a prerelease NPM package .tgz URL to the corresponding binary .zip URL
108
112
 
109
113
  const getBinaryUrlFromPrereleaseNpmUrl = npmUrl => {
110
114
  let parsed;
@@ -37,7 +37,7 @@ const xvfb = require('../exec/xvfb');
37
37
 
38
38
  const state = require('./state');
39
39
 
40
- const VERIFY_TEST_RUNNER_TIMEOUT_MS = 30000;
40
+ const VERIFY_TEST_RUNNER_TIMEOUT_MS = +process.env.CYPRESS_VERIFY_TIMEOUT || 30000;
41
41
 
42
42
  const checkExecutable = binaryDir => {
43
43
  const executable = state.getPathToExecutable(binaryDir);
package/package.json CHANGED
@@ -1,25 +1,25 @@
1
1
  {
2
2
  "name": "cypress",
3
- "version": "9.0.0",
3
+ "version": "9.2.1",
4
4
  "main": "index.js",
5
5
  "scripts": {
6
6
  "postinstall": "node index.js --exec install",
7
7
  "size": "t=\"$(npm pack .)\"; wc -c \"${t}\"; tar tvf \"${t}\"; rm \"${t}\";"
8
8
  },
9
9
  "dependencies": {
10
- "@cypress/request": "^2.88.7",
10
+ "@cypress/request": "^2.88.10",
11
11
  "@cypress/xvfb": "^1.2.4",
12
12
  "@types/node": "^14.14.31",
13
13
  "@types/sinonjs__fake-timers": "^6.0.2",
14
14
  "@types/sizzle": "^2.3.2",
15
15
  "arch": "^2.2.0",
16
16
  "blob-util": "^2.0.2",
17
- "bluebird": "^3.7.2",
17
+ "bluebird": "3.7.2",
18
18
  "cachedir": "^2.3.0",
19
19
  "chalk": "^4.1.0",
20
20
  "check-more-types": "^2.24.0",
21
21
  "cli-cursor": "^3.1.0",
22
- "cli-table3": "~0.6.0",
22
+ "cli-table3": "~0.6.1",
23
23
  "commander": "^5.1.0",
24
24
  "common-tags": "^1.8.0",
25
25
  "dayjs": "^1.10.4",
@@ -7,13 +7,38 @@ declare namespace Cypress {
7
7
  type HttpMethod = string
8
8
  type RequestBody = string | object
9
9
  type ViewportOrientation = 'portrait' | 'landscape'
10
- type PrevSubject = 'optional' | 'element' | 'document' | 'window'
10
+ type PrevSubject = keyof PrevSubjectMap
11
11
  type TestingType = 'e2e' | 'component'
12
12
  type PluginConfig = (on: PluginEvents, config: PluginConfigOptions) => void | ConfigOptions | Promise<ConfigOptions>
13
13
 
14
+ interface PrevSubjectMap<O = unknown> {
15
+ optional: O
16
+ element: JQuery
17
+ document: Document
18
+ window: Window
19
+ }
20
+
14
21
  interface CommandOptions {
15
22
  prevSubject: boolean | PrevSubject | PrevSubject[]
16
23
  }
24
+ interface CommandFn<T extends keyof ChainableMethods> {
25
+ (this: Mocha.Context, ...args: Parameters<ChainableMethods[T]>): ReturnType<ChainableMethods[T]> | void
26
+ }
27
+ interface CommandFnWithSubject<T extends keyof ChainableMethods, S> {
28
+ (this: Mocha.Context, prevSubject: S, ...args: Parameters<ChainableMethods[T]>): ReturnType<ChainableMethods[T]> | void
29
+ }
30
+ interface CommandOriginalFn<T extends keyof ChainableMethods> extends CallableFunction {
31
+ (...args: Parameters<ChainableMethods[T]>): ReturnType<ChainableMethods[T]>
32
+ }
33
+ interface CommandOriginalFnWithSubject<T extends keyof ChainableMethods, S> extends CallableFunction {
34
+ (prevSubject: S, ...args: Parameters<ChainableMethods[T]>): ReturnType<ChainableMethods[T]>
35
+ }
36
+ interface CommandFnWithOriginalFn<T extends keyof Chainable> {
37
+ (this: Mocha.Context, originalFn: CommandOriginalFn<T>, ...args: Parameters<ChainableMethods[T]>): ReturnType<ChainableMethods[T]> | void
38
+ }
39
+ interface CommandFnWithOriginalFnAndSubject<T extends keyof Chainable, S> {
40
+ (this: Mocha.Context, originalFn: CommandOriginalFnWithSubject<T, S>, prevSubject: S, ...args: Parameters<ChainableMethods[T]>): ReturnType<ChainableMethods[T]> | void
41
+ }
17
42
  interface ObjectLike {
18
43
  [key: string]: any
19
44
  }
@@ -294,6 +319,11 @@ declare namespace Cypress {
294
319
  */
295
320
  LocalStorage: LocalStorage
296
321
 
322
+ /**
323
+ * Internal class for session management.
324
+ */
325
+ session: Session
326
+
297
327
  /**
298
328
  * Current testing type, determined by the Test Runner chosen to run.
299
329
  */
@@ -328,7 +358,7 @@ declare namespace Cypress {
328
358
  // 60000
329
359
  ```
330
360
  */
331
- config<K extends keyof ConfigOptions>(key: K): ResolvedConfigOptions[K]
361
+ config<K extends keyof Config>(key: K): Config[K]
332
362
  /**
333
363
  * Sets one configuration value.
334
364
  * @see https://on.cypress.io/config
@@ -337,7 +367,7 @@ declare namespace Cypress {
337
367
  Cypress.config('viewportWidth', 800)
338
368
  ```
339
369
  */
340
- config<K extends keyof ConfigOptions>(key: K, value: ResolvedConfigOptions[K]): void
370
+ config<K extends keyof TestConfigOverrides>(key: K, value: TestConfigOverrides[K]): void
341
371
  /**
342
372
  * Sets multiple configuration values at once.
343
373
  * @see https://on.cypress.io/config
@@ -420,9 +450,16 @@ declare namespace Cypress {
420
450
  * @see https://on.cypress.io/api/commands
421
451
  */
422
452
  Commands: {
423
- add<T extends keyof Chainable>(name: T, fn: Chainable[T]): void
424
- add<T extends keyof Chainable>(name: T, options: CommandOptions, fn: Chainable[T]): void
425
- overwrite<T extends keyof Chainable>(name: T, fn: Chainable[T]): void
453
+ add<T extends keyof Chainable>(name: T, fn: CommandFn<T>): void
454
+ add<T extends keyof Chainable>(name: T, options: CommandOptions & {prevSubject: false}, fn: CommandFn<T>): void
455
+ add<T extends keyof Chainable, S extends PrevSubject>(
456
+ name: T, options: CommandOptions & { prevSubject: true | S | ['optional'] }, fn: CommandFnWithSubject<T, PrevSubjectMap[S]>,
457
+ ): void
458
+ add<T extends keyof Chainable, S extends PrevSubject>(
459
+ name: T, options: CommandOptions & { prevSubject: S[] }, fn: CommandFnWithSubject<T, PrevSubjectMap<void>[S]>,
460
+ ): void
461
+ overwrite<T extends keyof Chainable>(name: T, fn: CommandFnWithOriginalFn<T>): void
462
+ overwrite<T extends keyof Chainable, S extends PrevSubject>(name: T, fn: CommandFnWithOriginalFnAndSubject<T, PrevSubjectMap[S]>): void
426
463
  }
427
464
 
428
465
  /**
@@ -2209,12 +2246,9 @@ declare namespace Cypress {
2209
2246
  * @see https://on.cypress.io/writefile
2210
2247
  ```
2211
2248
  cy.writeFile('path/to/message.txt', 'Hello World')
2212
- .then((text) => {
2213
- expect(text).to.equal('Hello World') // true
2214
- })
2215
2249
  ```
2216
2250
  */
2217
- writeFile<C extends FileContents>(filePath: string, contents: C, encoding: Encodings): Chainable<C>
2251
+ writeFile(filePath: string, contents: FileContents, encoding: Encodings): Chainable<null>
2218
2252
  /**
2219
2253
  * Write to a file with the specified encoding and contents.
2220
2254
  *
@@ -2223,12 +2257,10 @@ declare namespace Cypress {
2223
2257
  cy.writeFile('path/to/ascii.txt', 'Hello World', {
2224
2258
  flag: 'a+',
2225
2259
  encoding: 'ascii'
2226
- }).then((text) => {
2227
- expect(text).to.equal('Hello World') // true
2228
2260
  })
2229
2261
  ```
2230
2262
  */
2231
- writeFile<C extends FileContents>(filePath: string, contents: C, options?: Partial<WriteFileOptions>): Chainable<C>
2263
+ writeFile(filePath: string, contents: FileContents, options?: Partial<WriteFileOptions & Timeoutable>): Chainable<null>
2232
2264
  /**
2233
2265
  * Write to a file with the specified encoding and contents.
2234
2266
  *
@@ -2238,12 +2270,10 @@ declare namespace Cypress {
2238
2270
  ```
2239
2271
  cy.writeFile('path/to/ascii.txt', 'Hello World', 'utf8', {
2240
2272
  flag: 'a+',
2241
- }).then((text) => {
2242
- expect(text).to.equal('Hello World') // true
2243
2273
  })
2244
2274
  ```
2245
2275
  */
2246
- writeFile<C extends FileContents>(filePath: string, contents: C, encoding: Encodings, options?: Partial<WriteFileOptions>): Chainable<C>
2276
+ writeFile(filePath: string, contents: FileContents, encoding: Encodings, options?: Partial<WriteFileOptions & Timeoutable>): Chainable<null>
2247
2277
 
2248
2278
  /**
2249
2279
  * jQuery library bound to the AUT
@@ -2255,6 +2285,12 @@ declare namespace Cypress {
2255
2285
  $$<TElement extends Element = HTMLElement>(selector: JQuery.Selector, context?: Element | Document | JQuery): JQuery<TElement>
2256
2286
  }
2257
2287
 
2288
+ type ChainableMethods<Subject = any> = {
2289
+ [P in keyof Chainable<Subject>]: Chainable<Subject>[P] extends ((...args: any[]) => any)
2290
+ ? Chainable<Subject>[P]
2291
+ : never
2292
+ }
2293
+
2258
2294
  interface SinonSpyAgent<A extends sinon.SinonSpy> {
2259
2295
  log(shouldOutput?: boolean): Omit<A, 'withArgs'> & Agent<A>
2260
2296
 
@@ -2886,7 +2922,7 @@ declare namespace Cypress {
2886
2922
  xhrUrl: string
2887
2923
  }
2888
2924
 
2889
- interface TestConfigOverrides extends Partial<Pick<ConfigOptions, 'animationDistanceThreshold' | 'baseUrl' | 'defaultCommandTimeout' | 'env' | 'execTimeout' | 'includeShadowDom' | 'requestTimeout' | 'responseTimeout' | 'retries' | 'scrollBehavior' | 'taskTimeout' | 'viewportHeight' | 'viewportWidth' | 'waitForAnimations'>> {
2925
+ interface TestConfigOverrides extends Partial<Pick<ConfigOptions, 'animationDistanceThreshold' | 'baseUrl' | 'blockHosts' | 'defaultCommandTimeout' | 'env' | 'execTimeout' | 'includeShadowDom' | 'numTestsKeptInMemory' | 'pageLoadTimeout' | 'redirectionLimit' | 'requestTimeout' | 'responseTimeout' | 'retries' | 'screenshotOnRunFailure' | 'slowTestThreshold' | 'scrollBehavior' | 'taskTimeout' | 'viewportHeight' | 'viewportWidth' | 'waitForAnimations'>> {
2890
2926
  browser?: IsBrowserMatcher | IsBrowserMatcher[]
2891
2927
  keystrokeDelay?: number
2892
2928
  }
@@ -3086,6 +3122,11 @@ declare namespace Cypress {
3086
3122
  onAnyAbort(route: RouteOptions, proxy: any): void
3087
3123
  }
3088
3124
 
3125
+ interface Session {
3126
+ // Clear all saved sessions and re-run the current spec file.
3127
+ clearAllSavedSessions: () => Promise<void>
3128
+ }
3129
+
3089
3130
  type SameSiteStatus = 'no_restriction' | 'strict' | 'lax'
3090
3131
 
3091
3132
  interface SetCookieOptions extends Loggable, Timeoutable {