Browse Source

feat: introduce APIs to update envs from tests and recursive resolution

Andrew Bastin 3 years ago
parent
commit
59c6e21636

+ 99 - 6
packages/hoppscotch-app/helpers/RequestRunner.ts

@@ -1,8 +1,10 @@
 import { Observable } from "rxjs"
 import { filter } from "rxjs/operators"
 import { chain, right, TaskEither } from "fp-ts/lib/TaskEither"
-import { pipe } from "fp-ts/function"
+import { flow, pipe } from "fp-ts/function"
 import * as O from "fp-ts/Option"
+import * as A from "fp-ts/Array"
+import { Environment } from "@hoppscotch/data"
 import { runTestScript, TestDescriptor } from "@hoppscotch/js-sandbox"
 import { isRight } from "fp-ts/Either"
 import {
@@ -15,6 +17,15 @@ import { createRESTNetworkRequestStream } from "./network"
 import { HoppTestData, HoppTestResult } from "./types/HoppTestResult"
 import { isJSONContentType } from "./utils/contenttypes"
 import { getRESTRequest, setRESTTestResults } from "~/newstore/RESTSession"
+import {
+  environmentsStore,
+  getCurrentEnvironment,
+  getEnviroment,
+  getGlobalVariables,
+  setGlobalEnvVariables,
+  updateEnvironment,
+} from "~/newstore/environments"
+import { TestResult } from "~/../hoppscotch-js-sandbox/lib/test-runner"
 
 const getTestableBody = (
   res: HoppRESTResponse & { type: "success" | "fail" }
@@ -43,6 +54,11 @@ const getTestableBody = (
   return x
 }
 
+const combineEnvVariables = (env: {
+  global: Environment["variables"]
+  selected: Environment["variables"]
+}) => [...env.selected, ...env.global]
+
 export const runRESTRequest$ = (): TaskEither<
   string | Error,
   Observable<HoppRESTResponse>
@@ -55,7 +71,7 @@ export const runRESTRequest$ = (): TaskEither<
     chain((envs) => {
       const effectiveRequest = getEffectiveRESTRequest(getRESTRequest(), {
         name: "Env",
-        variables: envs,
+        variables: combineEnvVariables(envs),
       })
 
       const stream = createRESTNetworkRequestStream(effectiveRequest)
@@ -65,7 +81,7 @@ export const runRESTRequest$ = (): TaskEither<
         .pipe(filter((res) => res.type === "success" || res.type === "fail"))
         .subscribe(async (res) => {
           if (res.type === "success" || res.type === "fail") {
-            const runResult = await runTestScript(res.req.testScript, {
+            const runResult = await runTestScript(res.req.testScript, envs, {
               status: res.statusCode,
               body: getTestableBody(res),
               headers: res.headers,
@@ -73,11 +89,37 @@ export const runRESTRequest$ = (): TaskEither<
 
             if (isRight(runResult)) {
               setRESTTestResults(translateToSandboxTestResults(runResult.right))
+
+              setGlobalEnvVariables(runResult.right.envs.global)
+              if (environmentsStore.value.currentEnvironmentIndex !== -1) {
+                const env = getEnviroment(
+                  environmentsStore.value.currentEnvironmentIndex
+                )
+                updateEnvironment(
+                  environmentsStore.value.currentEnvironmentIndex,
+                  {
+                    name: env.name,
+                    variables: runResult.right.envs.selected,
+                  }
+                )
+              }
             } else {
               setRESTTestResults({
                 description: "",
                 expectResults: [],
                 tests: [],
+                envDiff: {
+                  global: {
+                    additions: [],
+                    deletions: [],
+                    updations: [],
+                  },
+                  selected: {
+                    additions: [],
+                    deletions: [],
+                    updations: [],
+                  },
+                },
                 scriptError: true,
               })
             }
@@ -90,8 +132,37 @@ export const runRESTRequest$ = (): TaskEither<
     })
   )
 
+const getAddedEnvVariables = (
+  current: Environment["variables"],
+  updated: Environment["variables"]
+) => updated.filter((x) => current.findIndex((y) => y.key === x.key) === -1)
+
+const getRemovedEnvVariables = (
+  current: Environment["variables"],
+  updated: Environment["variables"]
+) => current.filter((x) => updated.findIndex((y) => y.key === x.key) === -1)
+
+const getUpdatedEnvVariables = (
+  current: Environment["variables"],
+  updated: Environment["variables"]
+) =>
+  pipe(
+    updated,
+    A.filterMap(
+      flow(
+        O.fromPredicate(
+          (x) => current.findIndex((y) => y.key === x.key) !== -1
+        ),
+        O.map((x) => ({
+          ...x,
+          previousValue: current.find((y) => x.key === y.key)!.value,
+        }))
+      )
+    )
+  )
+
 function translateToSandboxTestResults(
-  testDesc: TestDescriptor
+  testDesc: TestResult & { tests: TestDescriptor }
 ): HoppTestResult {
   const translateChildTests = (child: TestDescriptor): HoppTestData => {
     return {
@@ -100,10 +171,32 @@ function translateToSandboxTestResults(
       tests: child.children.map(translateChildTests),
     }
   }
+
+  const globals = getGlobalVariables()
+  const env = getCurrentEnvironment()
+
   return {
     description: "",
-    expectResults: testDesc.expectResults,
-    tests: testDesc.children.map(translateChildTests),
+    expectResults: testDesc.tests.expectResults,
+    tests: testDesc.tests.children.map(translateChildTests),
     scriptError: false,
+    envDiff: {
+      global: {
+        additions: getAddedEnvVariables(globals, testDesc.envs.global),
+        deletions: getRemovedEnvVariables(globals, testDesc.envs.global),
+        updations: getUpdatedEnvVariables(globals, testDesc.envs.global),
+      },
+      selected: {
+        additions: getAddedEnvVariables(env.variables, testDesc.envs.selected),
+        deletions: getRemovedEnvVariables(
+          env.variables,
+          testDesc.envs.selected
+        ),
+        updations: getUpdatedEnvVariables(
+          env.variables,
+          testDesc.envs.selected
+        ),
+      },
+    },
   }
 }

+ 9 - 19
packages/hoppscotch-app/helpers/preRequest.ts

@@ -1,29 +1,19 @@
 import { runPreRequestScript } from "@hoppscotch/js-sandbox"
+import { Environment } from "@hoppscotch/data"
 import {
   getCurrentEnvironment,
   getGlobalVariables,
 } from "~/newstore/environments"
 
-export const getCombinedEnvVariables = () => {
-  const variables: { key: string; value: string }[] = [...getGlobalVariables()]
-
-  for (const variable of getCurrentEnvironment().variables) {
-    const index = variables.findIndex((v) => variable.key === v.key)
-
-    if (index === -1) {
-      variables.push({
-        key: variable.key,
-        value: variable.value,
-      })
-    } else {
-      variables[index].value = variable.value
-    }
-  }
-
-  return variables
-}
+export const getCombinedEnvVariables = () => ({
+  global: getGlobalVariables(),
+  selected: getCurrentEnvironment().variables,
+})
 
 export const getFinalEnvsFromPreRequest = (
   script: string,
-  envs: { key: string; value: string }[]
+  envs: {
+    global: Environment["variables"]
+    selected: Environment["variables"]
+  }
 ) => runPreRequestScript(script, envs)

+ 19 - 0
packages/hoppscotch-app/helpers/types/HoppTestResult.ts

@@ -1,3 +1,5 @@
+import { Environment } from "@hoppscotch/data"
+
 export type HoppTestExpectResult = {
   status: "fail" | "pass" | "error"
   message: string
@@ -14,4 +16,21 @@ export type HoppTestResult = {
   expectResults: HoppTestExpectResult[]
   description: string
   scriptError: boolean
+
+  envDiff: {
+    global: {
+      additions: Environment["variables"]
+      updations: Array<
+        Environment["variables"][number] & { previousValue: string }
+      >
+      deletions: Environment["variables"]
+    }
+    selected: {
+      additions: Environment["variables"]
+      updations: Array<
+        Environment["variables"][number] & { previousValue: string }
+      >
+      deletions: Environment["variables"]
+    }
+  }
 }

+ 1 - 1
packages/hoppscotch-app/package.json

@@ -57,7 +57,7 @@
     "@codemirror/view": "^0.19.0",
     "@hoppscotch/codemirror-lang-graphql": "workspace:^0.1.0",
     "@hoppscotch/data": "workspace:^0.4.0",
-    "@hoppscotch/js-sandbox": "workspace:^1.0.0",
+    "@hoppscotch/js-sandbox": "workspace:^2.0.0",
     "@nuxtjs/axios": "^5.13.6",
     "@nuxtjs/composition-api": "^0.31.0",
     "@nuxtjs/gtm": "^2.4.0",

+ 3 - 2
packages/hoppscotch-js-sandbox/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@hoppscotch/js-sandbox",
-  "version": "1.0.0",
+  "version": "2.0.0",
   "description": "JavaScript sandboxes for running external scripts used by Hoppscotch clients",
   "main": "./lib/index.js",
   "types": "./lib/",
@@ -35,7 +35,8 @@
   "dependencies": {
     "fp-ts": "^2.11.8",
     "lodash": "^4.17.21",
-    "quickjs-emscripten": "^0.15.0"
+    "quickjs-emscripten": "^0.15.0",
+    "@hoppscotch/data": "workspace:^0.4.0"
   },
   "devDependencies": {
     "@digitak/esrun": "^3.1.2",

+ 41 - 23
packages/hoppscotch-js-sandbox/src/__tests__/preRequest.spec.ts

@@ -8,15 +8,21 @@ describe("execPreRequestScript", () => {
         `
           pw.env.set("bob", "newbob")
         `,
-        [
-          { key: "bob", value: "oldbob" },
-          { key: "foo", value: "bar" },
-        ]
+        {
+          global: [],
+          selected: [
+            { key: "bob", value: "oldbob" },
+            { key: "foo", value: "bar" },
+          ],
+        }
       )()
-    ).resolves.toEqualRight([
-      { key: "bob", value: "newbob" },
-      { key: "foo", value: "bar" },
-    ])
+    ).resolves.toEqualRight({
+      global: [],
+      selected: [
+        { key: "bob", value: "newbob" },
+        { key: "foo", value: "bar" },
+      ],
+    })
   })
 
   test("fails if the key is not a string", () => {
@@ -25,10 +31,13 @@ describe("execPreRequestScript", () => {
         `
           pw.env.set(10, "newbob")
         `,
-        [
-          { key: "bob", value: "oldbob" },
-          { key: "foo", value: "bar" },
-        ]
+        {
+          global: [],
+          selected: [
+            { key: "bob", value: "oldbob" },
+            { key: "foo", value: "bar" },
+          ],
+        }
       )()
     ).resolves.toBeLeft()
   })
@@ -39,10 +48,13 @@ describe("execPreRequestScript", () => {
         `
           pw.env.set("bob", 10)
         `,
-        [
-          { key: "bob", value: "oldbob" },
-          { key: "foo", value: "bar" },
-        ]
+        {
+          global: [],
+          selected: [
+            { key: "bob", value: "oldbob" },
+            { key: "foo", value: "bar" },
+          ],
+        }
       )()
     ).resolves.toBeLeft()
   })
@@ -51,12 +63,15 @@ describe("execPreRequestScript", () => {
     return expect(
       execPreRequestScript(
         `
-          pw.env.set("bob", 
+          pw.env.set("bob",
         `,
-        [
-          { key: "bob", value: "oldbob" },
-          { key: "foo", value: "bar" },
-        ]
+        {
+          global: [],
+          selected: [
+            { key: "bob", value: "oldbob" },
+            { key: "foo", value: "bar" },
+          ],
+        }
       )()
     ).resolves.toBeLeft()
   })
@@ -67,8 +82,11 @@ describe("execPreRequestScript", () => {
         `
           pw.env.set("foo", "bar")
         `,
-        []
+        { selected: [], global: [] }
       )()
-    ).resolves.toEqualRight([{ key: "foo", value: "bar" }])
+    ).resolves.toEqualRight({
+      global: [],
+      selected: [{ key: "foo", value: "bar" }],
+    })
   })
 })

+ 178 - 0
packages/hoppscotch-js-sandbox/src/__tests__/testing/envs/get.spec.ts

@@ -0,0 +1,178 @@
+import * as TE from "fp-ts/TaskEither"
+import { pipe } from "fp-ts/function"
+import { execTestScript, TestResponse, TestResult } from "../../../test-runner"
+
+import "@relmify/jest-fp-ts"
+
+const fakeResponse: TestResponse = {
+  status: 200,
+  body: "hoi",
+  headers: [],
+}
+
+const func = (script: string, envs: TestResult["envs"]) =>
+  pipe(
+    execTestScript(script, envs, fakeResponse),
+    TE.map((x) => x.tests)
+  )
+
+describe("pw.env.get", () => {
+  test("returns the correct value for an existing selected environment value", () => {
+    return expect(
+      func(
+        `
+          const data = pw.env.get("a")
+          pw.expect(data).toBe("b")
+      `,
+        {
+          global: [],
+          selected: [
+            {
+              key: "a",
+              value: "b",
+            },
+          ],
+        }
+      )()
+    ).resolves.toEqualRight([
+      expect.objectContaining({
+        expectResults: [
+          {
+            status: "pass",
+            message: "Expected 'b' to be 'b'",
+          },
+        ],
+      }),
+    ])
+  })
+
+  test("returns the correct value for an existing global environment value", () => {
+    return expect(
+      func(
+        `
+          const data = pw.env.get("a")
+          pw.expect(data).toBe("b")
+      `,
+        {
+          global: [
+            {
+              key: "a",
+              value: "b",
+            },
+          ],
+          selected: [],
+        }
+      )()
+    ).resolves.toEqualRight([
+      expect.objectContaining({
+        expectResults: [
+          {
+            status: "pass",
+            message: "Expected 'b' to be 'b'",
+          },
+        ],
+      }),
+    ])
+  })
+
+  test("returns undefined for a key that is not present in both selected or environment", () => {
+    return expect(
+      func(
+        `
+          const data = pw.env.get("a")
+          pw.expect(data).toBe(undefined)
+      `,
+        {
+          global: [],
+          selected: [],
+        }
+      )()
+    ).resolves.toEqualRight([
+      expect.objectContaining({
+        expectResults: [
+          {
+            status: "pass",
+            message: "Expected 'undefined' to be 'undefined'",
+          },
+        ],
+      }),
+    ])
+  })
+
+  test("returns the value defined in selected environment if it is also present in global", () => {
+    return expect(
+      func(
+        `
+          const data = pw.env.get("a")
+          pw.expect(data).toBe("selected val")
+      `,
+        {
+          global: [
+            {
+              key: "a",
+              value: "global val",
+            },
+          ],
+          selected: [
+            {
+              key: "a",
+              value: "selected val",
+            },
+          ],
+        }
+      )()
+    ).resolves.toEqualRight([
+      expect.objectContaining({
+        expectResults: [
+          {
+            status: "pass",
+            message: "Expected 'selected val' to be 'selected val'",
+          },
+        ],
+      }),
+    ])
+  })
+
+  test("does not resolve environment values", () => {
+    return expect(
+      func(
+        `
+          const data = pw.env.get("a")
+          pw.expect(data).toBe("<<hello>>")
+      `,
+        {
+          global: [],
+          selected: [
+            {
+              key: "a",
+              value: "<<hello>>",
+            },
+          ],
+        }
+      )()
+    ).resolves.toEqualRight([
+      expect.objectContaining({
+        expectResults: [
+          {
+            status: "pass",
+            message: "Expected '<<hello>>' to be '<<hello>>'",
+          },
+        ],
+      }),
+    ])
+  })
+
+  test("errors if the key is not a string", () => {
+    return expect(
+      func(
+        `
+          const data = pw.env.get(5)
+      `,
+        {
+          global: [],
+          selected: [],
+        }
+      )()
+    ).resolves.toBeLeft()
+  })
+})

+ 219 - 0
packages/hoppscotch-js-sandbox/src/__tests__/testing/envs/getResolve.spec.ts

@@ -0,0 +1,219 @@
+import * as TE from "fp-ts/TaskEither"
+import { pipe } from "fp-ts/function"
+import { execTestScript, TestResponse, TestResult } from "../../../test-runner"
+
+import "@relmify/jest-fp-ts"
+
+const fakeResponse: TestResponse = {
+  status: 200,
+  body: "hoi",
+  headers: [],
+}
+
+const func = (script: string, envs: TestResult["envs"]) =>
+  pipe(
+    execTestScript(script, envs, fakeResponse),
+    TE.map((x) => x.tests),
+    TE.mapLeft((x) => {
+      console.log(x)
+      return x
+    })
+  )
+
+describe("pw.env.getResolve", () => {
+  test("returns the correct value for an existing selected environment value", () => {
+    return expect(
+      func(
+        `
+          const data = pw.env.getResolve("a")
+          pw.expect(data).toBe("b")
+      `,
+        {
+          global: [],
+          selected: [
+            {
+              key: "a",
+              value: "b",
+            },
+          ],
+        }
+      )()
+    ).resolves.toEqualRight([
+      expect.objectContaining({
+        expectResults: [
+          {
+            status: "pass",
+            message: "Expected 'b' to be 'b'",
+          },
+        ],
+      }),
+    ])
+  })
+
+  test("returns the correct value for an existing global environment value", () => {
+    return expect(
+      func(
+        `
+          const data = pw.env.getResolve("a")
+          pw.expect(data).toBe("b")
+      `,
+        {
+          global: [
+            {
+              key: "a",
+              value: "b",
+            },
+          ],
+          selected: [],
+        }
+      )()
+    ).resolves.toEqualRight([
+      expect.objectContaining({
+        expectResults: [
+          {
+            status: "pass",
+            message: "Expected 'b' to be 'b'",
+          },
+        ],
+      }),
+    ])
+  })
+
+  test("returns undefined for a key that is not present in both selected or environment", () => {
+    return expect(
+      func(
+        `
+          const data = pw.env.getResolve("a")
+          pw.expect(data).toBe(undefined)
+      `,
+        {
+          global: [],
+          selected: [],
+        }
+      )()
+    ).resolves.toEqualRight([
+      expect.objectContaining({
+        expectResults: [
+          {
+            status: "pass",
+            message: "Expected 'undefined' to be 'undefined'",
+          },
+        ],
+      }),
+    ])
+  })
+
+  test("returns the value defined in selected environment if it is also present in global", () => {
+    return expect(
+      func(
+        `
+          const data = pw.env.getResolve("a")
+          pw.expect(data).toBe("selected val")
+      `,
+        {
+          global: [
+            {
+              key: "a",
+              value: "global val",
+            },
+          ],
+          selected: [
+            {
+              key: "a",
+              value: "selected val",
+            },
+          ],
+        }
+      )()
+    ).resolves.toEqualRight([
+      expect.objectContaining({
+        expectResults: [
+          {
+            status: "pass",
+            message: "Expected 'selected val' to be 'selected val'",
+          },
+        ],
+      }),
+    ])
+  })
+
+  test("resolve environment values", () => {
+    return expect(
+      func(
+        `
+          const data = pw.env.getResolve("a")
+          pw.expect(data).toBe("there")
+      `,
+        {
+          global: [],
+          selected: [
+            {
+              key: "a",
+              value: "<<hello>>",
+            },
+            {
+              key: "hello",
+              value: "there",
+            },
+          ],
+        }
+      )()
+    ).resolves.toEqualRight([
+      expect.objectContaining({
+        expectResults: [
+          {
+            status: "pass",
+            message: "Expected 'there' to be 'there'",
+          },
+        ],
+      }),
+    ])
+  })
+
+  test("returns unresolved value on infinite loop in resolution", () => {
+    return expect(
+      func(
+        `
+          const data = pw.env.getResolve("a")
+          pw.expect(data).toBe("<<hello>>")
+      `,
+        {
+          global: [],
+          selected: [
+            {
+              key: "a",
+              value: "<<hello>>",
+            },
+            {
+              key: "hello",
+              value: "<<a>>",
+            },
+          ],
+        }
+      )()
+    ).resolves.toEqualRight([
+      expect.objectContaining({
+        expectResults: [
+          {
+            status: "pass",
+            message: "Expected '<<hello>>' to be '<<hello>>'",
+          },
+        ],
+      }),
+    ])
+  })
+
+  test("errors if the key is not a string", () => {
+    return expect(
+      func(
+        `
+          const data = pw.env.getResolve(5)
+      `,
+        {
+          global: [],
+          selected: [],
+        }
+      )()
+    ).resolves.toBeLeft()
+  })
+})

+ 156 - 0
packages/hoppscotch-js-sandbox/src/__tests__/testing/envs/resolve.spec.ts

@@ -0,0 +1,156 @@
+import { pipe } from "fp-ts/function"
+import * as TE from "fp-ts/TaskEither"
+import { execTestScript, TestResponse, TestResult } from "../../../test-runner"
+
+const fakeResponse: TestResponse = {
+  status: 200,
+  body: "hoi",
+  headers: [],
+}
+
+const func = (script: string, envs: TestResult["envs"]) =>
+  pipe(
+    execTestScript(script, envs, fakeResponse),
+    TE.map((x) => x.tests)
+  )
+
+describe("pw.env.resolve", () => {
+  test("value should be a string", () => {
+    return expect(
+      func(
+        `
+          pw.env.resolve(5)
+        `,
+        {
+          global: [],
+          selected: [],
+        }
+      )()
+    ).resolves.toBeLeft()
+  })
+
+  test("resolves global variables correctly", () => {
+    return expect(
+      func(
+        `
+          const data = pw.env.resolve("<<hello>>")
+          pw.expect(data).toBe("there")
+        `,
+        {
+          global: [
+            {
+              key: "hello",
+              value: "there",
+            },
+          ],
+          selected: [],
+        }
+      )()
+    ).resolves.toEqualRight([
+      expect.objectContaining({
+        expectResults: [
+          {
+            status: "pass",
+            message: "Expected 'there' to be 'there'",
+          },
+        ],
+      }),
+    ])
+  })
+
+  test("resolves selected env variables correctly", () => {
+    return expect(
+      func(
+        `
+          const data = pw.env.resolve("<<hello>>")
+          pw.expect(data).toBe("there")
+        `,
+        {
+          global: [],
+          selected: [
+            {
+              key: "hello",
+              value: "there",
+            },
+          ],
+        }
+      )()
+    ).resolves.toEqualRight([
+      expect.objectContaining({
+        expectResults: [
+          {
+            status: "pass",
+            message: "Expected 'there' to be 'there'",
+          },
+        ],
+      }),
+    ])
+  })
+
+  test("chooses selected env variable over global variables when both have same variable", () => {
+    return expect(
+      func(
+        `
+          const data = pw.env.resolve("<<hello>>")
+          pw.expect(data).toBe("there")
+        `,
+        {
+          global: [
+            {
+              key: "hello",
+              value: "yo",
+            },
+          ],
+          selected: [
+            {
+              key: "hello",
+              value: "there",
+            },
+          ],
+        }
+      )()
+    ).resolves.toEqualRight([
+      expect.objectContaining({
+        expectResults: [
+          {
+            status: "pass",
+            message: "Expected 'there' to be 'there'",
+          },
+        ],
+      }),
+    ])
+  })
+
+  test("if infinite loop in resolution, abandons resolutions altogether", () => {
+    return expect(
+      func(
+        `
+          const data = pw.env.resolve("<<hello>>")
+          pw.expect(data).toBe("<<hello>>")
+        `,
+        {
+          global: [],
+          selected: [
+            {
+              key: "hello",
+              value: "<<there>>",
+            },
+            {
+              key: "there",
+              value: "<<hello>>",
+            },
+          ],
+        }
+      )()
+    ).resolves.toEqualRight([
+      expect.objectContaining({
+        expectResults: [
+          {
+            status: "pass",
+            message: "Expected '<<hello>>' to be '<<hello>>'",
+          },
+        ],
+      }),
+    ])
+  })
+})

+ 208 - 0
packages/hoppscotch-js-sandbox/src/__tests__/testing/envs/set.spec.ts

@@ -0,0 +1,208 @@
+import * as TE from "fp-ts/TaskEither"
+import { pipe } from "fp-ts/function"
+import { execTestScript, TestResponse, TestResult } from "../../../test-runner"
+
+const fakeResponse: TestResponse = {
+  status: 200,
+  body: "hoi",
+  headers: [],
+}
+
+const func = (script: string, envs: TestResult["envs"]) =>
+  pipe(
+    execTestScript(script, envs, fakeResponse),
+    TE.map((x) => x.envs)
+  )
+
+const funcTest = (script: string, envs: TestResult["envs"]) =>
+  pipe(
+    execTestScript(script, envs, fakeResponse),
+    TE.map((x) => x.tests)
+  )
+
+describe("pw.env.set", () => {
+  test("updates the selected environment variable correctly", () => {
+    return expect(
+      func(
+        `
+          pw.env.set("a", "c")
+        `,
+        {
+          global: [],
+          selected: [
+            {
+              key: "a",
+              value: "b",
+            },
+          ],
+        }
+      )()
+    ).resolves.toEqualRight(
+      expect.objectContaining({
+        selected: [
+          {
+            key: "a",
+            value: "c",
+          },
+        ],
+      })
+    )
+  })
+
+  test("updates the global environment variable correctly", () => {
+    return expect(
+      func(
+        `
+          pw.env.set("a", "c")
+        `,
+        {
+          global: [
+            {
+              key: "a",
+              value: "b",
+            },
+          ],
+          selected: [],
+        }
+      )()
+    ).resolves.toEqualRight(
+      expect.objectContaining({
+        global: [
+          {
+            key: "a",
+            value: "c",
+          },
+        ],
+      })
+    )
+  })
+
+  test("updates the selected environment if env present in both", () => {
+    return expect(
+      func(
+        `
+          pw.env.set("a", "c")
+        `,
+        {
+          global: [
+            {
+              key: "a",
+              value: "b",
+            },
+          ],
+          selected: [
+            {
+              key: "a",
+              value: "d",
+            },
+          ],
+        }
+      )()
+    ).resolves.toEqualRight(
+      expect.objectContaining({
+        global: [
+          {
+            key: "a",
+            value: "b",
+          },
+        ],
+        selected: [
+          {
+            key: "a",
+            value: "c",
+          },
+        ],
+      })
+    )
+  })
+
+  test("non existent keys are created in the selected environment", () => {
+    return expect(
+      func(
+        `
+          pw.env.set("a", "c")
+        `,
+        {
+          global: [],
+          selected: [],
+        }
+      )()
+    ).resolves.toEqualRight(
+      expect.objectContaining({
+        global: [],
+        selected: [
+          {
+            key: "a",
+            value: "c",
+          },
+        ],
+      })
+    )
+  })
+
+  test("keys should be a string", () => {
+    return expect(
+      func(
+        `
+          pw.env.set(5, "c")
+        `,
+        {
+          global: [],
+          selected: [],
+        }
+      )()
+    ).resolves.toBeLeft()
+  })
+
+  test("values should be a string", () => {
+    return expect(
+      func(
+        `
+          pw.env.set("a", 5)
+        `,
+        {
+          global: [],
+          selected: [],
+        }
+      )()
+    ).resolves.toBeLeft()
+  })
+
+  test("both keys and values should be strings", () => {
+    return expect(
+      func(
+        `
+          pw.env.set(5, 5)
+        `,
+        {
+          global: [],
+          selected: [],
+        }
+      )()
+    ).resolves.toBeLeft()
+  })
+
+  test("set environment values are reflected in the script execution", () => {
+    return expect(
+      funcTest(
+        `
+          pw.env.set("a", "b")
+          pw.expect(pw.env.get("a")).toBe("b")
+        `,
+        {
+          global: [],
+          selected: [],
+        }
+      )()
+    ).resolves.toEqualRight([
+      expect.objectContaining({
+        expectResults: [
+          {
+            status: "pass",
+            message: "Expected 'b' to be 'b'",
+          },
+        ],
+      }),
+    ])
+  })
+})

Some files were not shown because too many files changed in this diff