streamlit

Форк
0
252 строки · 8.6 Кб
1
/**
2
 * Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022-2024)
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16

17
// ***********************************************
18
// This example commands.js shows you how to
19
// create various custom commands and overwrite
20
// existing commands.
21
//
22
// For more comprehensive examples of custom
23
// commands please read more here:
24
// https://on.cypress.io/custom-commands
25
// ***********************************************
26
//
27
//
28
// -- This is a parent command --
29
// Cypress.Commands.add("login", (email, password) => { ... })
30
//
31
//
32
// -- This is a child command --
33
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
34
//
35
//
36
// -- This is a dual command --
37
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
38
//
39
//
40
// -- This is will overwrite an existing command --
41
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
42

43
import path from "path"
44
import * as _ from "lodash"
45

46
// https://github.com/palmerhq/cypress-image-snapshot#installation
47
import { addMatchImageSnapshotCommand } from "cypress-image-snapshot/command"
48
import "cypress-file-upload"
49

50
/**
51
 * Returns an OS and device-pixel-ratio specific snapshot folder, e.g. <rootDir>/cypress/snapshots/darwin/2x
52
 * We use per-OS snapshots to account for rendering differences in fonts and UI widgets.
53
 * We use per-DPR snapshots to account for rendering differences in image dimensions.
54
 */
55
function getSnapshotFolder() {
56
  const devicePixelRatio = Cypress.env("devicePixelRatio") || 2
57
  return path.join(
58
    "cypress",
59
    "snapshots",
60
    Cypress.platform,
61
    devicePixelRatio + "x"
62
  )
63
}
64

65
addMatchImageSnapshotCommand({
66
  customSnapshotsDir: getSnapshotFolder(),
67
  failureThreshold: 0.01, // Threshold for entire image
68
  failureThresholdType: "percent", // Percent of image or number of pixels
69
})
70

71
Cypress.Commands.add("openSettings", () => {
72
  cy.get("#MainMenu > button").click()
73
  cy.get('[data-testid="main-menu-list"]').should("contain.text", "Settings")
74
  cy.get('[data-testid="main-menu-list"]')
75
    .contains("Settings")
76
    .click({ force: true })
77
})
78

79
Cypress.Commands.add("changeTheme", theme => {
80
  cy.openSettings()
81
  cy.get('[data-baseweb="modal"] .stSelectbox').then(el => {
82
    cy.wrap(el).find("input").click()
83
    cy.get("li").contains(theme).click()
84
  })
85
  cy.get('[data-baseweb="modal"] [aria-label="Close"]').click()
86
})
87

88
/**
89
 * Normal usage:
90
 *
91
 *   cy.get("my selector").first().matchImageSnapshot("my filename")
92
 *
93
 * This means the "subject" in the matchThemedSnapshots function will be the
94
 * result of cy.get("my selector").first(). However, in some cases the subject
95
 * detaches from the DOM when we change themes (this seems to happen with the
96
 * image in the staticfiles_app test, for example), causing Cypress to fail.
97
 * When that happens, you can fix the issue by passing a getSubject function
98
 * to this command to get the subject from the DOM again, like this:
99
 *
100
 *   cy.get("body").matchImageSnapshot(
101
 *     "my filename", {},
102
 *     () => cy.get("my selector").first()
103
 *   )
104
 *
105
 * Note that the example above uses cy.get("body") because that part of the
106
 * incantation doesn't actually matter. It just needs to exist.
107
 */
108
Cypress.Commands.add(
109
  "matchThemedSnapshots",
110
  { prevSubject: true },
111
  (subject, name, options, getSubject) => {
112
    const testName = name || Cypress.mocha.getRunner().suite.ctx.test.title
113
    const setStates = () => {
114
      const { focus } = _.pick(options, ["focus"])
115
      if (focus) {
116
        cy.get(subject).within(() => {
117
          cy.get(focus).focus()
118
        })
119
      }
120
    }
121

122
    if (!getSubject) {
123
      getSubject = () => cy.wrap(subject)
124
    }
125

126
    // Get dark mode snapshot first. Taking light mode snapshot first
127
    // for some reason ends up comparing dark with light
128
    cy.changeTheme("Dark")
129
    setStates()
130
    getSubject().matchImageSnapshot(`${testName}-dark`, {
131
      ...options,
132
      force: false,
133
    })
134

135
    // Revert back to light mode
136
    cy.changeTheme("Light")
137
    setStates()
138
    getSubject().matchImageSnapshot(testName, { ...options, force: false })
139
    cy.screenshot()
140
  }
141
)
142

143
// Calling trigger before capturing the snapshot forces Cypress to very Actionability.
144
// https://docs.cypress.io/guides/core-concepts/interacting-with-elements.html#Actionability
145
// This fixes the issue where snapshots are cutoff or the wrong element is captured.
146
Cypress.Commands.overwrite(
147
  "matchImageSnapshot",
148
  (originalFn, subject, name, options) => {
149
    cy.wrap(subject).trigger("blur", _.pick(options, ["force"]))
150

151
    const headerHeight = 2.875 // In rem
152
    const fontSizeMedium = 16 // In px
153
    cy.get(subject).scrollIntoView({
154
      offset: {
155
        top: -1 * headerHeight * fontSizeMedium,
156
      },
157
    })
158

159
    return originalFn(subject, name, options)
160
  }
161
)
162

163
Cypress.Commands.add("loadApp", (appUrl, timeout) => {
164
  cy.visit(appUrl)
165

166
  cy.waitForScriptFinish(timeout)
167
})
168

169
Cypress.Commands.add("waitForScriptFinish", (timeout = 20000) => {
170
  // Wait until we know the script has started. We determine this by checking
171
  // whether the app is in notRunning state. (The data-teststate attribute goes
172
  // through the sequence "initial" -> "running" -> "notRunning")
173
  cy.get("[data-testid='stApp'][data-teststate='notRunning']", {
174
    timeout,
175
  }).should("exist")
176
})
177

178
// Indexing into a list of elements produced by `cy.get()` may fail if not enough
179
// elements are rendered, but this does not prompt cypress to retry the `get` call,
180
// so the list will never update. This is a major cause of flakiness in tests.
181
// The solution is to use `should` to wait for enough elements to be available first.
182
// This is a convenience function for doing this automatically.
183
Cypress.Commands.add("getIndexed", (selector, index) =>
184
  cy
185
    .get(selector)
186
    .should("have.length.at.least", index + 1)
187
    .eq(index)
188
)
189

190
// The header at the top of the page can sometimes interfere when we are
191
// attempting to take snapshots. This command removes the problematic parts to
192
// avoid this issue.
193
Cypress.Commands.add("prepForElementSnapshots", () => {
194
  // Look for the ribbon and if its found,
195
  // make the ribbon decoration line disappear as it can occasionally get
196
  // caught when a snapshot is taken.
197
  cy.get(".stApp").then($body => {
198
    if ($body.find("[data-testid='stDecoration']").length > 0) {
199
      cy.get("[data-testid='stDecoration']").invoke("css", "display", "none")
200
    }
201
  })
202

203
  // Similarly, the header styling can sometimes interfere with the snapshot
204
  // for elements near the top of the page.
205
  cy.get(".stApp > header").invoke("css", "background", "none")
206
  cy.get(".stApp > header").invoke("css", "backdropFilter", "none")
207
})
208

209
// Allows the user to execute code within the iframe itself
210
// This is useful for testing/changing examples of Streamlit embeddings
211
Cypress.Commands.add(
212
  "iframe",
213
  { prevSubject: "element" },
214
  ($iframe, callback = () => {}) => {
215
    // For more info on targeting inside iframes refer to this GitHub issue:
216
    // https://github.com/cypress-io/cypress/issues/136
217
    cy.log("Getting iframe body")
218

219
    return cy
220
      .wrap($iframe)
221
      .should(iframe => expect(iframe.contents().find("body")).to.exist)
222
      .then(iframe => cy.wrap(iframe.contents().find("body")))
223
      .within({}, callback)
224
  }
225
)
226

227
// Rerun the script by simulating the user pressing the 'r' key.
228
Cypress.Commands.add("rerunScript", () => {
229
  cy.get(".stApp [data-testid='stHeader']").trigger("keypress", {
230
    keyCode: 82, // "r"
231
    which: 82, // "r"
232
    force: true,
233
  })
234
})
235

236
Cypress.Commands.add("waitForRerun", () => {
237
  cy.get("[data-testid='stStatusWidget']", { timeout: 10000 }).should("exist")
238
  cy.get("[data-testid='stStatusWidget']", { timeout: 10000 }).should(
239
    "not.exist"
240
  )
241
})
242

243
// https://github.com/quasarframework/quasar/issues/2233
244
// This error means that ResizeObserver was not able to deliver all observations within a single animation frame
245
// It is benign (your site will not break).
246
const resizeObserverLoopErrRe = /^[^(ResizeObserver loop limit exceeded)]/
247
Cypress.on("uncaught:exception", err => {
248
  /* returning false here prevents Cypress from failing the test */
249
  if (resizeObserverLoopErrRe.test(err.message)) {
250
    return false
251
  }
252
})
253

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.