cypress 9.1.1 → 9.3.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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