chore: introduce expect.configure (#22533)
This commit is contained in:
		
							parent
							
								
									76a2afc836
								
							
						
					
					
						commit
						a1007bbe2c
					
				|  | @ -121,7 +121,17 @@ The same works with soft assertions: | ||||||
| expect.soft(value, 'my soft assertion').toBe(56); | expect.soft(value, 'my soft assertion').toBe(56); | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| ## Polling | ## expect.configurte | ||||||
|  | 
 | ||||||
|  | You can create your own pre-configured `expect` instance to have its own | ||||||
|  | defaults such as `timeout`, `soft` and `poll`. | ||||||
|  | 
 | ||||||
|  | ```js | ||||||
|  | const slowExpect = expect.configure({ timeout: 10000 }); | ||||||
|  | await slowExpect(locator).toHaveText('Submit); | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ## expect.poll | ||||||
| 
 | 
 | ||||||
| You can convert any synchronous `expect` to an asynchronous polling one using `expect.poll`. | You can convert any synchronous `expect` to an asynchronous polling one using `expect.poll`. | ||||||
| 
 | 
 | ||||||
|  | @ -152,7 +162,7 @@ await expect.poll(async () => { | ||||||
| }).toBe(200); | }).toBe(200); | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| ## Retrying | ## expect.toPass | ||||||
| 
 | 
 | ||||||
| You can retry blocks of code until they are passing successfully. | You can retry blocks of code until they are passing successfully. | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -34,10 +34,18 @@ export function currentlyLoadingFileSuite() { | ||||||
|   return currentFileSuite; |   return currentFileSuite; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | let currentExpectConfigureTimeout: number | undefined; | ||||||
|  | 
 | ||||||
|  | export function setCurrentExpectConfigureTimeout(timeout: number | undefined) { | ||||||
|  |   currentExpectConfigureTimeout = timeout; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| export function currentExpectTimeout(options: { timeout?: number }) { | export function currentExpectTimeout(options: { timeout?: number }) { | ||||||
|   const testInfo = currentTestInfo(); |   const testInfo = currentTestInfo(); | ||||||
|   if (options.timeout !== undefined) |   if (options.timeout !== undefined) | ||||||
|     return options.timeout; |     return options.timeout; | ||||||
|  |   if (currentExpectConfigureTimeout !== undefined) | ||||||
|  |     return currentExpectConfigureTimeout; | ||||||
|   let defaultExpectTimeout = testInfo?._projectInternal?.expect?.timeout; |   let defaultExpectTimeout = testInfo?._projectInternal?.expect?.timeout; | ||||||
|   if (typeof defaultExpectTimeout === 'undefined') |   if (typeof defaultExpectTimeout === 'undefined') | ||||||
|     defaultExpectTimeout = 5000; |     defaultExpectTimeout = 5000; | ||||||
|  |  | ||||||
|  | @ -18,6 +18,7 @@ import { | ||||||
|   captureRawStack, |   captureRawStack, | ||||||
|   createAfterActionTraceEventForExpect, |   createAfterActionTraceEventForExpect, | ||||||
|   createBeforeActionTraceEventForExpect, |   createBeforeActionTraceEventForExpect, | ||||||
|  |   isString, | ||||||
|   pollAgainstTimeout } from 'playwright-core/lib/utils'; |   pollAgainstTimeout } from 'playwright-core/lib/utils'; | ||||||
| import type { ExpectZone } from 'playwright-core/lib/utils'; | import type { ExpectZone } from 'playwright-core/lib/utils'; | ||||||
| import { | import { | ||||||
|  | @ -48,7 +49,7 @@ import { | ||||||
| } from './matchers'; | } from './matchers'; | ||||||
| import { toMatchSnapshot, toHaveScreenshot, toHaveScreenshotStepTitle } from './toMatchSnapshot'; | import { toMatchSnapshot, toHaveScreenshot, toHaveScreenshotStepTitle } from './toMatchSnapshot'; | ||||||
| import type { Expect } from '../../types/test'; | import type { Expect } from '../../types/test'; | ||||||
| import { currentTestInfo, currentExpectTimeout } from '../common/globals'; | import { currentTestInfo, currentExpectTimeout, setCurrentExpectConfigureTimeout } from '../common/globals'; | ||||||
| import { filteredStackTrace, serializeError, stringifyStackFrames, trimLongString } from '../util'; | import { filteredStackTrace, serializeError, stringifyStackFrames, trimLongString } from '../util'; | ||||||
| import { | import { | ||||||
|   expect as expectLibrary, |   expect as expectLibrary, | ||||||
|  | @ -106,28 +107,58 @@ export const printReceivedStringContainExpectedResult = ( | ||||||
| 
 | 
 | ||||||
| // #endregion
 | // #endregion
 | ||||||
| 
 | 
 | ||||||
| type ExpectMessageOrOptions = undefined | string | { message?: string, timeout?: number, intervals?: number[] }; | type ExpectMessage = string | { message?: string }; | ||||||
| 
 | 
 | ||||||
| function createExpect(actual: unknown, messageOrOptions: ExpectMessageOrOptions, isSoft: boolean, isPoll: boolean, generator?: Generator): any { | function createMatchers(actual: unknown, info: ExpectMetaInfo): any { | ||||||
|   return new Proxy(expectLibrary(actual), new ExpectMetaInfoProxyHandler(messageOrOptions, isSoft, isPoll, generator)); |   return new Proxy(expectLibrary(actual), new ExpectMetaInfoProxyHandler(info)); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export const expect: Expect = new Proxy(expectLibrary, { | function createExpect(info: ExpectMetaInfo) { | ||||||
|   apply: function(target: any, thisArg: any, argumentsList: [actual: unknown, messageOrOptions: ExpectMessageOrOptions]) { |   const expect: Expect = new Proxy(expectLibrary, { | ||||||
|     const [actual, messageOrOptions] = argumentsList; |     apply: function(target: any, thisArg: any, argumentsList: [unknown, ExpectMessage?]) { | ||||||
|     return createExpect(actual, messageOrOptions, false /* isSoft */, false /* isPoll */); |       const [actual, messageOrOptions] = argumentsList; | ||||||
|   } |       const message = isString(messageOrOptions) ? messageOrOptions : messageOrOptions?.message || info.message; | ||||||
| }); |       const newInfo = { ...info, message }; | ||||||
|  |       if (newInfo.isPoll) { | ||||||
|  |         if (typeof actual !== 'function') | ||||||
|  |           throw new Error('`expect.poll()` accepts only function as a first argument'); | ||||||
|  |         newInfo.generator = actual as any; | ||||||
|  |       } | ||||||
|  |       return createMatchers(actual, newInfo); | ||||||
|  |     } | ||||||
|  |   }); | ||||||
| 
 | 
 | ||||||
| expect.soft = (actual: unknown, messageOrOptions: ExpectMessageOrOptions) => { |   expect.soft = (actual: unknown, messageOrOptions?: ExpectMessage) => { | ||||||
|   return createExpect(actual, messageOrOptions, true /* isSoft */, false /* isPoll */); |     return expect.configure({ soft: true })(actual, messageOrOptions) as any; | ||||||
| }; |   }; | ||||||
| 
 | 
 | ||||||
| expect.poll = (actual: unknown, messageOrOptions: ExpectMessageOrOptions) => { |   expect.poll = (actual: unknown, messageOrOptions?: ExpectMessage & { timeout?: number, intervals?: number[] }) => { | ||||||
|   if (typeof actual !== 'function') |     const poll = isString(messageOrOptions) ? {} : messageOrOptions || {}; | ||||||
|     throw new Error('`expect.poll()` accepts only function as a first argument'); |     return expect.configure({ poll })(actual, messageOrOptions) as any; | ||||||
|   return createExpect(actual, messageOrOptions, false /* isSoft */, true /* isPoll */, actual as any); |   }; | ||||||
| }; | 
 | ||||||
|  |   expect.configure = (configuration: { message?: string, timeout?: number, soft?: boolean, poll?: boolean | { timeout?: number, intervals?: number[] } }) => { | ||||||
|  |     const newInfo = { ...info }; | ||||||
|  |     if ('message' in configuration) | ||||||
|  |       newInfo.message = configuration.message; | ||||||
|  |     if ('timeout' in configuration) | ||||||
|  |       newInfo.timeout = configuration.timeout; | ||||||
|  |     if ('soft' in configuration) | ||||||
|  |       newInfo.isSoft = configuration.soft; | ||||||
|  |     if ('poll' in configuration) { | ||||||
|  |       newInfo.isPoll = !!configuration.poll; | ||||||
|  |       if (typeof configuration.poll === 'object') { | ||||||
|  |         newInfo.pollTimeout = configuration.poll.timeout; | ||||||
|  |         newInfo.pollIntervals = configuration.poll.intervals; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return createExpect(newInfo); | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   return expect; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export const expect: Expect = createExpect({}); | ||||||
| 
 | 
 | ||||||
| expectLibrary.setState({ expand: false }); | expectLibrary.setState({ expand: false }); | ||||||
| 
 | 
 | ||||||
|  | @ -168,10 +199,10 @@ type Generator = () => any; | ||||||
| 
 | 
 | ||||||
| type ExpectMetaInfo = { | type ExpectMetaInfo = { | ||||||
|   message?: string; |   message?: string; | ||||||
|   isNot: boolean; |   isNot?: boolean; | ||||||
|   isSoft: boolean; |   isSoft?: boolean; | ||||||
|   isPoll: boolean; |   isPoll?: boolean; | ||||||
|   nameTokens: string[]; |   timeout?: number; | ||||||
|   pollTimeout?: number; |   pollTimeout?: number; | ||||||
|   pollIntervals?: number[]; |   pollIntervals?: number[]; | ||||||
|   generator?: Generator; |   generator?: Generator; | ||||||
|  | @ -180,15 +211,8 @@ type ExpectMetaInfo = { | ||||||
| class ExpectMetaInfoProxyHandler implements ProxyHandler<any> { | class ExpectMetaInfoProxyHandler implements ProxyHandler<any> { | ||||||
|   private _info: ExpectMetaInfo; |   private _info: ExpectMetaInfo; | ||||||
| 
 | 
 | ||||||
|   constructor(messageOrOptions: ExpectMessageOrOptions, isSoft: boolean, isPoll: boolean, generator?: Generator) { |   constructor(info: ExpectMetaInfo) { | ||||||
|     this._info = { isSoft, isPoll, generator, isNot: false, nameTokens: [] }; |     this._info = { ...info }; | ||||||
|     if (typeof messageOrOptions === 'string') { |  | ||||||
|       this._info.message = messageOrOptions; |  | ||||||
|     } else { |  | ||||||
|       this._info.message = messageOrOptions?.message; |  | ||||||
|       this._info.pollTimeout = messageOrOptions?.timeout; |  | ||||||
|       this._info.pollIntervals = messageOrOptions?.intervals; |  | ||||||
|     } |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   get(target: Object, matcherName: string | symbol, receiver: any): any { |   get(target: Object, matcherName: string | symbol, receiver: any): any { | ||||||
|  | @ -205,7 +229,7 @@ class ExpectMetaInfoProxyHandler implements ProxyHandler<any> { | ||||||
|     if (this._info.isPoll) { |     if (this._info.isPoll) { | ||||||
|       if ((customAsyncMatchers as any)[matcherName] || matcherName === 'resolves' || matcherName === 'rejects') |       if ((customAsyncMatchers as any)[matcherName] || matcherName === 'resolves' || matcherName === 'rejects') | ||||||
|         throw new Error(`\`expect.poll()\` does not support "${matcherName}" matcher.`); |         throw new Error(`\`expect.poll()\` does not support "${matcherName}" matcher.`); | ||||||
|       matcher = (...args: any[]) => pollMatcher(matcherName, this._info.isNot, this._info.pollIntervals, currentExpectTimeout({ timeout: this._info.pollTimeout }), this._info.generator!, ...args); |       matcher = (...args: any[]) => pollMatcher(matcherName, !!this._info.isNot, this._info.pollIntervals, currentExpectTimeout({ timeout: this._info.pollTimeout }), this._info.generator!, ...args); | ||||||
|     } |     } | ||||||
|     return (...args: any[]) => { |     return (...args: any[]) => { | ||||||
|       const testInfo = currentTestInfo(); |       const testInfo = currentTestInfo(); | ||||||
|  | @ -278,6 +302,8 @@ class ExpectMetaInfoProxyHandler implements ProxyHandler<any> { | ||||||
|           try { |           try { | ||||||
|             const expectZone: ExpectZone = { title: defaultTitle, wallTime }; |             const expectZone: ExpectZone = { title: defaultTitle, wallTime }; | ||||||
|             await zones.run<ExpectZone, any>('expectZone', expectZone, async () => { |             await zones.run<ExpectZone, any>('expectZone', expectZone, async () => { | ||||||
|  |               // We assume that the matcher will read the current expect timeout the first thing.
 | ||||||
|  |               setCurrentExpectConfigureTimeout(this._info.timeout); | ||||||
|               await matcher.call(target, ...args); |               await matcher.call(target, ...args); | ||||||
|             }); |             }); | ||||||
|             finalizer(); |             finalizer(); | ||||||
|  |  | ||||||
|  | @ -4654,6 +4654,12 @@ export type Expect = { | ||||||
|      not: BaseMatchers<Promise<void>, T>; |      not: BaseMatchers<Promise<void>, T>; | ||||||
|   }; |   }; | ||||||
|   extend(matchers: any): void; |   extend(matchers: any): void; | ||||||
|  |   configure: (configuration: { | ||||||
|  |     message?: string, | ||||||
|  |     timeout?: number, | ||||||
|  |     soft?: boolean, | ||||||
|  |     poll?: boolean | { timeout?: number, intervals?: number[] }, | ||||||
|  |   }) => Expect; | ||||||
|   getState(): { |   getState(): { | ||||||
|     expand?: boolean; |     expand?: boolean; | ||||||
|     isNot: boolean; |     isNot: boolean; | ||||||
|  |  | ||||||
|  | @ -0,0 +1,160 @@ | ||||||
|  | /** | ||||||
|  |  * Copyright Microsoft Corporation. All rights reserved. | ||||||
|  |  * | ||||||
|  |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  |  * you may not use this file except in compliance with the License. | ||||||
|  |  * You may obtain a copy of the License at | ||||||
|  |  * | ||||||
|  |  *     http://www.apache.org/licenses/LICENSE-2.0
 | ||||||
|  |  * | ||||||
|  |  * Unless required by applicable law or agreed to in writing, software | ||||||
|  |  * distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  |  * See the License for the specific language governing permissions and | ||||||
|  |  * limitations under the License. | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | import { test, expect } from './playwright-test-fixtures'; | ||||||
|  | 
 | ||||||
|  | test('should configure timeout', async ({ runInlineTest }) => { | ||||||
|  |   const result = await runInlineTest({ | ||||||
|  |     'a.spec.ts': ` | ||||||
|  |       import { test, expect } from '@playwright/test'; | ||||||
|  |       const fastExpect = expect.configure({ timeout: 1 }); | ||||||
|  |       test('pass', async ({ page }) => { | ||||||
|  |         const time = performance.now(); | ||||||
|  |         try { | ||||||
|  |           await fastExpect(page.locator('li')).toBeVisible(); | ||||||
|  |         } catch (e) { | ||||||
|  |           expect(performance.now() - time).toBeLessThan(5000); | ||||||
|  |         } | ||||||
|  |       }); | ||||||
|  |     ` | ||||||
|  |   }); | ||||||
|  |   expect(result.exitCode).toBe(0); | ||||||
|  |   expect(result.passed).toBe(1); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | test('should configure message', async ({ runInlineTest }) => { | ||||||
|  |   const result = await runInlineTest({ | ||||||
|  |     'expect-test.spec.ts': ` | ||||||
|  |       import { test, expect } from '@playwright/test'; | ||||||
|  |       const namedExpect = expect.configure({ message: 'x-foo must be visible' }); | ||||||
|  |       test('custom expect message', async ({page}) => { | ||||||
|  |         await namedExpect(page.locator('x-foo')).toBeVisible({timeout: 1}); | ||||||
|  |       }); | ||||||
|  |     ` | ||||||
|  |   }); | ||||||
|  |   expect(result.exitCode).toBe(1); | ||||||
|  |   expect(result.passed).toBe(0); | ||||||
|  |   expect(result.output).toContain([ | ||||||
|  |     `    Error: x-foo must be visible`, | ||||||
|  |     ``, | ||||||
|  |     `    Call log:`, | ||||||
|  |   ].join('\n')); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | test('should prefer local message', async ({ runInlineTest }) => { | ||||||
|  |   const result = await runInlineTest({ | ||||||
|  |     'expect-test.spec.ts': ` | ||||||
|  |       import { test, expect } from '@playwright/test'; | ||||||
|  |       const namedExpect = expect.configure({ message: 'x-foo must be visible' }); | ||||||
|  |       test('custom expect message', async ({page}) => { | ||||||
|  |         await namedExpect(page.locator('x-foo'), { message: 'overridden' }).toBeVisible({timeout: 1}); | ||||||
|  |       }); | ||||||
|  |     ` | ||||||
|  |   }); | ||||||
|  |   expect(result.exitCode).toBe(1); | ||||||
|  |   expect(result.passed).toBe(0); | ||||||
|  |   expect(result.output).toContain([ | ||||||
|  |     `    Error: overridden`, | ||||||
|  |     ``, | ||||||
|  |     `    Call log:`, | ||||||
|  |   ].join('\n')); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | test('should configure soft', async ({ runInlineTest }) => { | ||||||
|  |   const result = await runInlineTest({ | ||||||
|  |     'a.spec.ts': ` | ||||||
|  |       import { test, expect } from '@playwright/test'; | ||||||
|  |       const softExpect = expect.configure({ soft: true }); | ||||||
|  |       test('should work', () => { | ||||||
|  |         softExpect(1+1).toBe(3); | ||||||
|  |         console.log('woof-woof'); | ||||||
|  |       }); | ||||||
|  |     ` | ||||||
|  |   }); | ||||||
|  |   expect(result.exitCode).toBe(1); | ||||||
|  |   expect(result.output).toContain('woof-woof'); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | test('should configure poll', async ({ runInlineTest }) => { | ||||||
|  |   const result = await runInlineTest({ | ||||||
|  |     'a.spec.ts': ` | ||||||
|  |       import { test, expect } from '@playwright/test'; | ||||||
|  |       const pollingExpect = expect.configure({ poll: { timeout: 1000, intervals: [0, 10000] } }); | ||||||
|  |       test('should fail', async () => { | ||||||
|  |         let probes = 0; | ||||||
|  |         const startTime = Date.now(); | ||||||
|  |         await pollingExpect(() => ++probes).toBe(3).catch(() => {}); | ||||||
|  |         // Probe at 0 and epsilon.
 | ||||||
|  |         expect(probes).toBe(2); | ||||||
|  |         expect(Date.now() - startTime).toBeLessThan(5000); | ||||||
|  |       }); | ||||||
|  |     ` | ||||||
|  |   }); | ||||||
|  |   expect(result.exitCode).toBe(0); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | test('should chain configure', async ({ runInlineTest }) => { | ||||||
|  |   const result = await runInlineTest({ | ||||||
|  |     'expect-test.spec.ts': ` | ||||||
|  |       import { test, expect } from '@playwright/test'; | ||||||
|  |       const slowExpect = expect.configure({ timeout: 1 }); | ||||||
|  |       const slowAndSoftExpect = slowExpect.configure({ soft: true }); | ||||||
|  |       test('custom expect message', async ({page}) => { | ||||||
|  |         await slowAndSoftExpect(page.locator('x-foo')).toBeVisible({timeout: 1}); | ||||||
|  |         console.log('%% woof-woof'); | ||||||
|  |       }); | ||||||
|  |     ` | ||||||
|  |   }); | ||||||
|  |   expect(result.exitCode).toBe(1); | ||||||
|  |   expect(result.passed).toBe(0); | ||||||
|  |   expect(result.outputLines).toEqual(['woof-woof']); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | test('should cancel effect', async ({ runInlineTest }) => { | ||||||
|  |   const result = await runInlineTest({ | ||||||
|  |     'a.spec.ts': ` | ||||||
|  |       import { test, expect } from '@playwright/test'; | ||||||
|  |       const softExpect = expect.configure({ soft: true }); | ||||||
|  |       const normalExpect = expect.configure({ soft: false }); | ||||||
|  |       test('should work', () => { | ||||||
|  |         normalExpect(1+1).toBe(3); | ||||||
|  |         console.log('%% woof-woof'); | ||||||
|  |       }); | ||||||
|  |     ` | ||||||
|  |   }); | ||||||
|  |   expect(result.exitCode).toBe(1); | ||||||
|  |   expect(result.outputLines).toEqual([]); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | test('should configure soft poll', async ({ runInlineTest }) => { | ||||||
|  |   const result = await runInlineTest({ | ||||||
|  |     'a.spec.ts': ` | ||||||
|  |       import { test, expect } from '@playwright/test'; | ||||||
|  |       const pollingExpect = expect.configure({ soft: true, poll: { timeout: 1000, intervals: [0, 10000] } }); | ||||||
|  |       test('should fail', async () => { | ||||||
|  |         let probes = 0; | ||||||
|  |         const startTime = Date.now(); | ||||||
|  |         await pollingExpect(() => ++probes).toBe(3); | ||||||
|  |         // Probe at 0 and epsilon.
 | ||||||
|  |         expect(probes).toBe(2); | ||||||
|  |         expect(Date.now() - startTime).toBeLessThan(5000); | ||||||
|  |         console.log('%% woof-woof'); | ||||||
|  |       }); | ||||||
|  |     ` | ||||||
|  |   }); | ||||||
|  |   expect(result.exitCode).toBe(1); | ||||||
|  |   expect(result.outputLines).toEqual(['woof-woof']); | ||||||
|  | }); | ||||||
|  | @ -36,12 +36,12 @@ test('soft expects should work', async ({ runInlineTest }) => { | ||||||
|       import { test, expect } from '@playwright/test'; |       import { test, expect } from '@playwright/test'; | ||||||
|       test('should work', () => { |       test('should work', () => { | ||||||
|         test.expect.soft(1+1).toBe(3); |         test.expect.soft(1+1).toBe(3); | ||||||
|         console.log('woof-woof'); |         console.log('%% woof-woof'); | ||||||
|       }); |       }); | ||||||
|     ` |     ` | ||||||
|   }); |   }); | ||||||
|   expect(result.exitCode).toBe(1); |   expect(result.exitCode).toBe(1); | ||||||
|   expect(result.output).toContain('woof-woof'); |   expect(result.outputLines).toEqual(['woof-woof']); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| test('should report a mixture of soft and non-soft errors', async ({ runInlineTest }) => { | test('should report a mixture of soft and non-soft errors', async ({ runInlineTest }) => { | ||||||
|  |  | ||||||
|  | @ -342,6 +342,12 @@ export type Expect = { | ||||||
|      not: BaseMatchers<Promise<void>, T>; |      not: BaseMatchers<Promise<void>, T>; | ||||||
|   }; |   }; | ||||||
|   extend(matchers: any): void; |   extend(matchers: any): void; | ||||||
|  |   configure: (configuration: { | ||||||
|  |     message?: string, | ||||||
|  |     timeout?: number, | ||||||
|  |     soft?: boolean, | ||||||
|  |     poll?: boolean | { timeout?: number, intervals?: number[] }, | ||||||
|  |   }) => Expect; | ||||||
|   getState(): { |   getState(): { | ||||||
|     expand?: boolean; |     expand?: boolean; | ||||||
|     isNot: boolean; |     isNot: boolean; | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue