-
Notifications
You must be signed in to change notification settings - Fork 3.1k
/
experiments.ts
151 lines (131 loc) · 5.07 KB
/
experiments.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
import { get } from 'lodash'
/**
* Returns a single string with human-readable experiments.
```
const experimental = getExperimentsFromResolved(config.resolved)
const enabledExperiments = _.pickBy(experimental, (experiment) => experiment.enabled)
formatExperiments(enabledExperiments)
// "componentsTesting=true,featureB=false"
```
*/
export const formatExperiments = (exp: CypressExperiments) => {
return Object.keys(exp).map((name) => `${name}=${exp[name].value}`).join(',')
}
type CypressProject = unknown
/**
* Single experimental feature. Experiment is enabled
* if its value is different from the default value (coming from the config).
*/
interface CypressExperiment {
enabled: boolean // is the experiment enabled
value: unknown // current value
key: string // usually the config key used to control the experiment
name: string // short name of the experiment
summary: string // one or two line experiment summary
}
/**
* Collection of Cypress experiments
*/
interface CypressExperiments {
[key: string]: CypressExperiment
}
interface StringValues {
[key: string]: string
}
/**
* Keeps summaries of experiments. Each summary is 1 - 2 sentences
* describing the purpose of the experiment.
* When adding an experiment, add its summary text here.
*
* @example
```
{
experimentalFetchPolyfill: 'Polyfills `window.fetch` to enable Network spying and stubbing.'
}
```
*/
const _summaries: StringValues = {
experimentalFetchPolyfill: 'Polyfills `window.fetch` to enable Network spying and stubbing.',
experimentalInteractiveRunEvents: 'Allows listening to the `before:run`, `after:run`, `before:spec`, and `after:spec` events in the plugins file during interactive mode.',
experimentalSessionAndOrigin: 'Enables cross-origin and improved session support, including the `cy.origin` and `cy.session` commands.',
experimentalModifyObstructiveThirdPartyCode: 'Applies `modifyObstructiveCode` to third party `.html` and `.js`, removes subresource integrity, and modifies the user agent in Electron.',
experimentalSourceRewriting: 'Enables AST-based JS/HTML rewriting. This may fix issues caused by the existing regex-based JS/HTML replacement algorithm.',
experimentalStudio: 'Generate and save commands directly to your test suite by interacting with your app as an end user would.',
}
/**
* Keeps short names for experiments. When adding new experiments, add a short name.
* The name and summary will be shown in the Settings tab of the Desktop GUI.
* @example
```
{
experimentalFetchPolyfill: 'Fetch polyfill'
}
```
*/
const _names: StringValues = {
experimentalFetchPolyfill: 'Fetch Polyfill',
experimentalInteractiveRunEvents: 'Interactive Mode Run Events',
experimentalSessionAndOrigin: 'Cross-origin and Session',
experimentalModifyObstructiveThirdPartyCode: 'Modify Obstructive Third Party Code',
experimentalSourceRewriting: 'Improved Source Rewriting',
experimentalStudio: 'Studio',
}
/**
* Export this object for easy stubbing from end-to-end tests.
* If you cannot easily pass "names" and "summaries" arguments
* to "getExperimentsFromResolved" function, then use this
* object to change "experiments.names" and "experimental.summaries" objects.
*/
export const experimental = {
names: _names,
summaries: _summaries,
}
export const getExperimentsFromResolved = (resolvedConfig, names = experimental.names, summaries = experimental.summaries): CypressExperiments => {
const experiments: CypressExperiments = {}
if (!resolvedConfig) {
// no config - no experiments
// this is likely to happen during unit testing
return experiments
}
const isExperimentKey = (key) => key.startsWith('experimental')
const experimentalKeys = Object.keys(resolvedConfig).filter(isExperimentKey)
experimentalKeys.forEach((key) => {
const name = get(names, key)
if (!name) {
// ignore unknown experiments
return
}
const summary = get(summaries, key, 'top secret')
// it would be nice to have default value in the resolved config
experiments[key] = {
key,
value: resolvedConfig[key].value,
enabled: resolvedConfig[key].from !== 'default',
name,
summary,
}
})
return experiments
}
/**
* Looks at the resolved config, finds all keys that start with "experimental" prefix
* and have non-default values and returns a simple object with {key: {value, enabled}}
* where "on" is set to true if the value is different from default..
*/
export const getExperiments = (project: CypressProject, names = experimental.names, summaries = experimental.summaries): CypressExperiments => {
const resolvedEnv = get(project, 'resolvedConfig', {})
return getExperimentsFromResolved(resolvedEnv, names, summaries)
}
/**
* Whilelist known experiments here to avoid accidentally showing
* any config key that starts with "experimental" prefix
*/
// @ts-ignore
export const isKnownExperiment = (experiment, key) => {
return Object.keys(experimental.names).includes(key)
}
// exporting a single default object with methods
// helps make it is to stub and to test
export default {
getExperiments,
}