cypress 9.1.1 → 9.3.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,9 +77,10 @@ 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
- return `${endpoint}?platform=${platform}&arch=${arch()}`;
82
+ const pathTemplate = util.getEnv('CYPRESS_DOWNLOAD_PATH_TEMPLATE');
83
+ return pathTemplate ? pathTemplate.replace('${endpoint}', endpoint).replace('${platform}', platform).replace('${arch}', arch()) : `${endpoint}?platform=${platform}&arch=${arch()}`;
82
84
  };
83
85
 
84
86
  const getUrl = version => {
@@ -183,8 +185,18 @@ const downloadFromUrl = ({
183
185
  url,
184
186
  downloadDestination,
185
187
  progress,
186
- ca
188
+ ca,
189
+ version,
190
+ redirectTTL = defaultMaxRedirects
187
191
  }) => {
192
+ if (redirectTTL <= 0) {
193
+ return Promise.reject(new Error(stripIndent`
194
+ Failed downloading the Cypress binary.
195
+ There were too many redirects. The default allowance is ${defaultMaxRedirects}.
196
+ Maybe you got stuck in a redirect loop?
197
+ `));
198
+ }
199
+
188
200
  return new Promise((resolve, reject) => {
189
201
  const proxy = getProxyForUrlWithNpmConfig(url);
190
202
  debug('Downloading package', {
@@ -192,35 +204,24 @@ const downloadFromUrl = ({
192
204
  proxy,
193
205
  downloadDestination
194
206
  });
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
207
 
217
208
  if (ca) {
218
209
  debug('using custom CA details from npm config');
219
- reqOptions.agentOptions = {
220
- ca
221
- };
222
210
  }
223
211
 
212
+ const reqOptions = {
213
+ uri: url,
214
+ ...(proxy ? {
215
+ proxy
216
+ } : {}),
217
+ ...(ca ? {
218
+ agentOptions: {
219
+ ca
220
+ }
221
+ } : {}),
222
+ method: 'GET',
223
+ followRedirect: false
224
+ };
224
225
  const req = request(reqOptions); // closure
225
226
 
226
227
  let started = null;
@@ -248,18 +249,46 @@ const downloadFromUrl = ({
248
249
  // response headers
249
250
 
250
251
 
251
- started = new Date(); // if our status code does not start with 200
252
-
253
- if (!/^2/.test(response.statusCode)) {
252
+ started = new Date();
253
+
254
+ if (/^3/.test(response.statusCode)) {
255
+ const redirectVersion = response.headers['x-version'];
256
+ const redirectUrl = response.headers.location;
257
+ debug('redirect version:', redirectVersion);
258
+ debug('redirect url:', redirectUrl);
259
+ downloadFromUrl({
260
+ url: redirectUrl,
261
+ progress,
262
+ ca,
263
+ downloadDestination,
264
+ version: redirectVersion,
265
+ redirectTTL: redirectTTL - 1
266
+ }).then(resolve).catch(reject); // if our status code does not start with 200
267
+ } else if (!/^2/.test(response.statusCode)) {
254
268
  debug('response code %d', response.statusCode);
255
269
  const err = new Error(stripIndent`
256
270
  Failed downloading the Cypress binary.
257
271
  Response code: ${response.statusCode}
258
272
  Response message: ${response.statusMessage}
259
273
  `);
260
- reject(err);
274
+ reject(err); // status codes here are all 2xx
275
+ } else {
276
+ // We only enable this pipe connection when we know we've got a successful return
277
+ // and handle the completion with verify and resolve
278
+ // there was a possible race condition between end of request and close of writeStream
279
+ // that is made ordered with this Promise.all
280
+ Promise.all([new Promise(r => {
281
+ return response.pipe(fs.createWriteStream(downloadDestination).on('close', r));
282
+ }), new Promise(r => response.on('end', r))]).then(() => {
283
+ debug('downloading finished');
284
+ verifyDownloadedFile(downloadDestination, expectedSize, expectedChecksum).then(() => debug('verified')).then(() => resolve(version)).catch(reject);
285
+ });
261
286
  }
262
- }).on('error', reject).on('progress', state => {
287
+ }).on('error', e => {
288
+ if (e.code === 'ECONNRESET') return; // sometimes proxies give ECONNRESET but we don't care
289
+
290
+ reject(e);
291
+ }).on('progress', state => {
263
292
  // total time we've elapsed
264
293
  // starting on our first progress notification
265
294
  const elapsed = new Date() - started; // request-progress sends a value between 0 and 1
@@ -268,12 +297,6 @@ const downloadFromUrl = ({
268
297
  const eta = util.calculateEta(percentage, elapsed); // send up our percent and seconds remaining
269
298
 
270
299
  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
300
  });
278
301
  });
279
302
  };
@@ -288,7 +311,8 @@ const start = opts => {
288
311
  let {
289
312
  version,
290
313
  downloadDestination,
291
- progress
314
+ progress,
315
+ redirectTTL
292
316
  } = opts;
293
317
 
294
318
  if (!downloadDestination) {
@@ -316,7 +340,11 @@ const start = opts => {
316
340
  url,
317
341
  downloadDestination,
318
342
  progress,
319
- ca
343
+ ca,
344
+ version,
345
+ ...(redirectTTL ? {
346
+ redirectTTL
347
+ } : {})
320
348
  });
321
349
  }).catch(err => {
322
350
  return prettyDownloadErr(err, version);
@@ -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 = +util.getEnv('CYPRESS_VERIFY_TIMEOUT') || 30000;
41
41
 
42
42
  const checkExecutable = binaryDir => {
43
43
  const executable = state.getPathToExecutable(binaryDir);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cypress",
3
- "version": "9.1.1",
3
+ "version": "9.3.1",
4
4
  "main": "index.js",
5
5
  "scripts": {
6
6
  "postinstall": "node index.js --exec install",
@@ -10,16 +10,17 @@
10
10
  "@cypress/request": "^2.88.10",
11
11
  "@cypress/xvfb": "^1.2.4",
12
12
  "@types/node": "^14.14.31",
13
- "@types/sinonjs__fake-timers": "^6.0.2",
13
+ "@types/sinonjs__fake-timers": "8.1.1",
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
+ "buffer": "^5.6.0",
18
19
  "cachedir": "^2.3.0",
19
20
  "chalk": "^4.1.0",
20
21
  "check-more-types": "^2.24.0",
21
22
  "cli-cursor": "^3.1.0",
22
- "cli-table3": "~0.6.0",
23
+ "cli-table3": "~0.6.1",
23
24
  "commander": "^5.1.0",
24
25
  "common-tags": "^1.8.0",
25
26
  "dayjs": "^1.10.4",
@@ -27,3 +27,7 @@ interface NodeEventEmitter {
27
27
  prependOnceListener(event: string | symbol, listener: (...args: any[]) => void): this
28
28
  eventNames(): Array<string | symbol>
29
29
  }
30
+
31
+ // We use the Buffer class for dealing with binary data, especially around the
32
+ // selectFile interface.
33
+ type BufferType = import("buffer/").Buffer
@@ -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
  }
@@ -206,6 +231,15 @@ declare namespace Cypress {
206
231
  * Cypress.Blob.method()
207
232
  */
208
233
  Blob: BlobUtil.BlobUtilStatic
234
+ /**
235
+ * Cypress automatically includes a Buffer library and exposes it as Cypress.Buffer.
236
+ *
237
+ * @see https://on.cypress.io/buffer
238
+ * @see https://github.com/feross/buffer
239
+ * @example
240
+ * Cypress.Buffer.method()
241
+ */
242
+ Buffer: BufferType
209
243
  /**
210
244
  * Cypress automatically includes minimatch and exposes it as Cypress.minimatch.
211
245
  *
@@ -294,6 +328,11 @@ declare namespace Cypress {
294
328
  */
295
329
  LocalStorage: LocalStorage
296
330
 
331
+ /**
332
+ * Internal class for session management.
333
+ */
334
+ session: Session
335
+
297
336
  /**
298
337
  * Current testing type, determined by the Test Runner chosen to run.
299
338
  */
@@ -328,7 +367,7 @@ declare namespace Cypress {
328
367
  // 60000
329
368
  ```
330
369
  */
331
- config<K extends keyof ConfigOptions>(key: K): ResolvedConfigOptions[K]
370
+ config<K extends keyof Config>(key: K): Config[K]
332
371
  /**
333
372
  * Sets one configuration value.
334
373
  * @see https://on.cypress.io/config
@@ -337,7 +376,7 @@ declare namespace Cypress {
337
376
  Cypress.config('viewportWidth', 800)
338
377
  ```
339
378
  */
340
- config<K extends keyof ConfigOptions>(key: K, value: ResolvedConfigOptions[K]): void
379
+ config<K extends keyof TestConfigOverrides>(key: K, value: TestConfigOverrides[K]): void
341
380
  /**
342
381
  * Sets multiple configuration values at once.
343
382
  * @see https://on.cypress.io/config
@@ -420,9 +459,16 @@ declare namespace Cypress {
420
459
  * @see https://on.cypress.io/api/commands
421
460
  */
422
461
  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
462
+ add<T extends keyof Chainable>(name: T, fn: CommandFn<T>): void
463
+ add<T extends keyof Chainable>(name: T, options: CommandOptions & {prevSubject: false}, fn: CommandFn<T>): void
464
+ add<T extends keyof Chainable, S extends PrevSubject>(
465
+ name: T, options: CommandOptions & { prevSubject: true | S | ['optional'] }, fn: CommandFnWithSubject<T, PrevSubjectMap[S]>,
466
+ ): void
467
+ add<T extends keyof Chainable, S extends PrevSubject>(
468
+ name: T, options: CommandOptions & { prevSubject: S[] }, fn: CommandFnWithSubject<T, PrevSubjectMap<void>[S]>,
469
+ ): void
470
+ overwrite<T extends keyof Chainable>(name: T, fn: CommandFnWithOriginalFn<T>): void
471
+ overwrite<T extends keyof Chainable, S extends PrevSubject>(name: T, fn: CommandFnWithOriginalFnAndSubject<T, PrevSubjectMap[S]>): void
426
472
  }
427
473
 
428
474
  /**
@@ -625,6 +671,20 @@ declare namespace Cypress {
625
671
  */
626
672
  as(alias: string): Chainable<Subject>
627
673
 
674
+ /**
675
+ * Select a file with the given <input> element, or drag and drop a file over any DOM subject.
676
+ *
677
+ * @param {FileReference} files - The file(s) to select or drag onto this element.
678
+ * @see https://on.cypress.io/selectfile
679
+ * @example
680
+ * cy.get('input[type=file]').selectFile(Cypress.Buffer.from('text'))
681
+ * cy.get('input[type=file]').selectFile({
682
+ * fileName: 'users.json',
683
+ * fileContents: [{name: 'John Doe'}]
684
+ * })
685
+ */
686
+ selectFile(files: FileReference | FileReference[], options?: Partial<SelectFileOptions>): Chainable<Subject>
687
+
628
688
  /**
629
689
  * Blur a focused element. This element must currently be in focus.
630
690
  * If you want to ensure an element is focused before blurring,
@@ -2209,12 +2269,9 @@ declare namespace Cypress {
2209
2269
  * @see https://on.cypress.io/writefile
2210
2270
  ```
2211
2271
  cy.writeFile('path/to/message.txt', 'Hello World')
2212
- .then((text) => {
2213
- expect(text).to.equal('Hello World') // true
2214
- })
2215
2272
  ```
2216
2273
  */
2217
- writeFile<C extends FileContents>(filePath: string, contents: C, encoding: Encodings): Chainable<C>
2274
+ writeFile(filePath: string, contents: FileContents, encoding: Encodings): Chainable<null>
2218
2275
  /**
2219
2276
  * Write to a file with the specified encoding and contents.
2220
2277
  *
@@ -2223,12 +2280,10 @@ declare namespace Cypress {
2223
2280
  cy.writeFile('path/to/ascii.txt', 'Hello World', {
2224
2281
  flag: 'a+',
2225
2282
  encoding: 'ascii'
2226
- }).then((text) => {
2227
- expect(text).to.equal('Hello World') // true
2228
2283
  })
2229
2284
  ```
2230
2285
  */
2231
- writeFile<C extends FileContents>(filePath: string, contents: C, options?: Partial<WriteFileOptions>): Chainable<C>
2286
+ writeFile(filePath: string, contents: FileContents, options?: Partial<WriteFileOptions & Timeoutable>): Chainable<null>
2232
2287
  /**
2233
2288
  * Write to a file with the specified encoding and contents.
2234
2289
  *
@@ -2238,12 +2293,10 @@ declare namespace Cypress {
2238
2293
  ```
2239
2294
  cy.writeFile('path/to/ascii.txt', 'Hello World', 'utf8', {
2240
2295
  flag: 'a+',
2241
- }).then((text) => {
2242
- expect(text).to.equal('Hello World') // true
2243
2296
  })
2244
2297
  ```
2245
2298
  */
2246
- writeFile<C extends FileContents>(filePath: string, contents: C, encoding: Encodings, options?: Partial<WriteFileOptions>): Chainable<C>
2299
+ writeFile(filePath: string, contents: FileContents, encoding: Encodings, options?: Partial<WriteFileOptions & Timeoutable>): Chainable<null>
2247
2300
 
2248
2301
  /**
2249
2302
  * jQuery library bound to the AUT
@@ -2255,6 +2308,12 @@ declare namespace Cypress {
2255
2308
  $$<TElement extends Element = HTMLElement>(selector: JQuery.Selector, context?: Element | Document | JQuery): JQuery<TElement>
2256
2309
  }
2257
2310
 
2311
+ type ChainableMethods<Subject = any> = {
2312
+ [P in keyof Chainable<Subject>]: Chainable<Subject>[P] extends ((...args: any[]) => any)
2313
+ ? Chainable<Subject>[P]
2314
+ : never
2315
+ }
2316
+
2258
2317
  interface SinonSpyAgent<A extends sinon.SinonSpy> {
2259
2318
  log(shouldOutput?: boolean): Omit<A, 'withArgs'> & Agent<A>
2260
2319
 
@@ -2430,6 +2489,17 @@ declare namespace Cypress {
2430
2489
  scrollBehavior: scrollBehaviorOptions
2431
2490
  }
2432
2491
 
2492
+ interface SelectFileOptions extends Loggable, Timeoutable, ActionableOptions {
2493
+ /**
2494
+ * Which user action to perform. `select` matches selecting a file while
2495
+ * `drag-drop` matches dragging files from the operating system into the
2496
+ * document.
2497
+ *
2498
+ * @default 'select'
2499
+ */
2500
+ action: 'select' | 'drag-drop'
2501
+ }
2502
+
2433
2503
  interface BlurOptions extends Loggable, Forceable { }
2434
2504
 
2435
2505
  interface CheckOptions extends Loggable, Timeoutable, ActionableOptions {
@@ -2886,7 +2956,7 @@ declare namespace Cypress {
2886
2956
  xhrUrl: string
2887
2957
  }
2888
2958
 
2889
- interface TestConfigOverrides extends Partial<Pick<ConfigOptions, 'animationDistanceThreshold' | 'baseUrl' | 'defaultCommandTimeout' | 'env' | 'execTimeout' | 'includeShadowDom' | 'requestTimeout' | 'responseTimeout' | 'retries' | 'scrollBehavior' | 'taskTimeout' | 'viewportHeight' | 'viewportWidth' | 'waitForAnimations'>> {
2959
+ 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
2960
  browser?: IsBrowserMatcher | IsBrowserMatcher[]
2891
2961
  keystrokeDelay?: number
2892
2962
  }
@@ -3086,6 +3156,11 @@ declare namespace Cypress {
3086
3156
  onAnyAbort(route: RouteOptions, proxy: any): void
3087
3157
  }
3088
3158
 
3159
+ interface Session {
3160
+ // Clear all saved sessions and re-run the current spec file.
3161
+ clearAllSavedSessions: () => Promise<void>
3162
+ }
3163
+
3089
3164
  type SameSiteStatus = 'no_restriction' | 'strict' | 'lax'
3090
3165
 
3091
3166
  interface SetCookieOptions extends Loggable, Timeoutable {
@@ -5600,6 +5675,17 @@ declare namespace Cypress {
5600
5675
  stderr: string
5601
5676
  }
5602
5677
 
5678
+ type FileReference = string | BufferType | FileReferenceObject
5679
+ interface FileReferenceObject {
5680
+ /*
5681
+ * Buffers will be used as-is, while strings will be interpreted as an alias or a file path.
5682
+ * All other types will have `Buffer.from(JSON.stringify())` applied.
5683
+ */
5684
+ contents: any
5685
+ fileName?: string
5686
+ lastModified?: number
5687
+ }
5688
+
5603
5689
  interface LogAttrs {
5604
5690
  url: string
5605
5691
  consoleProps: ObjectLike