Repository: apollographql/apollo-server
Pull request #7431 (2023-03-08 20:22:36)

NEBULA-2126: Add new config options to Sandbox & Explorer landing pages
This should be rebased off of [this PR](https://github.com/apollographql/apollo-server/pull/7430), can you do that with forks?

## Context

[JIRA](https://apollographql.atlassian.net/browse/NEBULA-2126)

## What Changed?

We recently added the ability to 

1. make the embedded Sandbox endpoint static
2. [pass through shared headers](https://apollographql.atlassian.net/browse/NEBULA-2069) https://github.com/apollographql/embeddable-explorer/issues/214
3. [Allow users to embed an operation from a collection](https://apollographql.atlassian.net/browse/NEBULA-2125)
4. [Allow users to decide not to poll for updates initially](https://github.com/apollographql/embeddable-explorer/issues/185)

[in the embeddable-explorer repo](https://github.com/apollographql/embeddable-explorer/pull/220)

So this PR allows users to set the above configuration in their Sandbox. This PR also allows folks to pass operation ids to the Explorer, but no other changes are made the the Explorer.

## How to test

erm, run the landing pages locally!

there are already tests for the html functions

netlify[bot]: ### <span aria-hidden="true">👷</span> Deploy Preview for *apollo-server-docs* processing.


|  Name | Link |
|:-:|------------------------|
|<span aria-hidden="true">🔨</span> Latest commit | 98a350987abe29ac7d2da3b396a579af9940c6df |
|<span aria-hidden="true">🔍</span> Latest deploy log | https://app.netlify.com/sites/apollo-server-docs/deploys/6408ee8e0ea05100080f15d6 |
codesandbox-ci[bot]: This pull request is automatically built and testable in [CodeSandbox](https://codesandbox.io).

  To see build info of the built libraries, click [here](https://ci.codesandbox.io/status/apollographql/apollo-server/pr/7431/builds/354688) or the icon next to each commit SHA.

Latest deployment of this branch, based on commit 98a350987abe29ac7d2da3b396a579af9940c6df:

|Sandbox| Source |
|--|--|
|[Apollo Server Typescript](https://codesandbox.io/s/apollo-server-typescript-91rot9)| Configuration |
|[Apollo Server](https://codesandbox.io/s/apollo-server-po97ol)| Configuration |

mayakoneval: @trevor-scheer flagging that I am fixing this in this PR too https://github.com/apollographql/apollo-server/pull/7358/files#r1130206916

trevor-scheer: @mayakoneval just bureacracy left:
* Would you add a code snippet to your changeset to demonstrate usage of the new config options?
* Please break out the bugfix changeset to its own (as a patch) and provide some more description about why a user is interested in this fix (who's affected, what's broken)
* Update PR desc with bugfix info, can be duped from your changeset
mayakoneval: @trevor-scheer 

[Bug Fix PR here](https://github.com/apollographql/apollo-server/pull/7432)

This PR has commits from the bug fix PR and [This PR](https://github.com/apollographql/apollo-server/pull/7430) which is rebased off of the bug fix PR

added the snippet to this changeset as well 🙏🏻 
mayakoneval: TODO for maya in the morning, test several config options & make sure embedded explorer shows up as expected w the operation, collection, shared headers, uneditable url box etc
trevor-scheer: @mayakoneval q: what's the expected behavior for providing both this new config and a `document`? Now we might need some kind of either/or typing so users don't provide both options?

Optionally, document could become `string | { collectionId, operationId }` 🤔
mayakoneval: @trevor-scheer 
> what's the expected behavior for providing both this new config and a document? Now we might need some kind of either/or typing so users don't provide both options?

> Optionally, document could become string | { collectionId, operationId } 🤔

an operation from a collection includes headers, variables and a document actually. Now I am thinking I should have changed the types of the embed to be 

```
initialState: {
  document, headers, vars
} | { collectionId, operationId }
```

The issue with the landing pages is that we don't support `collectionId & operationId` in the AS landing page, just the embeds. Let me futz with the types to make it more clear if that makes sense to you. 


Patch:
diff --git a/.changeset/angry-weeks-flash.md b/.changeset/angry-weeks-flash.md
new file mode 100644
index 000000000..aac5c39a2
--- /dev/null
+++ b/.changeset/angry-weeks-flash.md
@@ -0,0 +1,39 @@
+---
+'@apollo/server': minor
+---
+
+In the Apollo Server Landing Page Local config, you can now automatically turn off autopolling on your endpoints as well as pass headers used to introspect your schema, embed an operation from a collection, and configure whether the endpoint input box is editable. In the Apollo Server Landing Page Prod config, you can embed an operation from a collection & we fixed a bug introduced in release 4.4.0
+
+Example of all new config options: 
+
+```
+const server = new ApolloServer({
+  typeDefs,
+  resolvers,
+  plugins: [
+    process.env.NODE_ENV === 'production'
+      ? ApolloServerPluginLandingPageProductionDefault({
+          graphRef: 'my-graph-id@my-graph-variant',
+          collectionId: 'abcdef',
+          operationId: '12345'
+          embed: true,
+          footer: false,
+        })
+      : ApolloServerPluginLandingPageLocalDefault({
+          collectionId: 'abcdef',
+          operationId: '12345'
+          embed: {
+            initialState: {
+              pollForSchemaUpdates: false,
+              sharedHeaders: {
+                "HeaderNeededForIntrospection": "ValueForIntrospection"
+              },
+            },
+            endpointIsEditable: true,
+          },
+          footer: false,
+        }),
+  ],
+});
+
+```
diff --git a/packages/server/src/plugin/landingPage/default/getEmbeddedHTML.ts b/packages/server/src/plugin/landingPage/default/getEmbeddedHTML.ts
index 85f9e79ae..51e9e4162 100644
--- a/packages/server/src/plugin/landingPage/default/getEmbeddedHTML.ts
+++ b/packages/server/src/plugin/landingPage/default/getEmbeddedHTML.ts
@@ -1,6 +1,6 @@
 import type {
+  ApolloServerPluginEmbeddedLandingPageLocalDefaultOptions,
   ApolloServerPluginEmbeddedLandingPageProductionDefaultOptions,
-  LandingPageConfig,
 } from './types';
 
 // This function turns an object into a string and replaces
@@ -12,7 +12,7 @@ import type {
 // `</script>` won't terminate the script block early.
 // (Perhaps we should have done this instead of the triple-encoding
 // of encodeConfig for the main landing page.)
-function getConfigStringForHtml(config: LandingPageConfig) {
+function getConfigStringForHtml(config: object) {
   return JSON.stringify(config)
     .replace('<', '\\u003c')
     .replace('>', '\\u003e')
@@ -33,6 +33,8 @@ export const getEmbeddedExplorerHTML = (
       document?: string;
       variables?: Record<string, any>;
       headers?: Record<string, string>;
+      collectionId?: string;
+      operationId?: string;
       displayOptions: {
         docsPanelState?: 'open' | 'closed'; // default to 'open',
         showHeadersAndEnvVars?: boolean; // default to `false`
@@ -57,9 +59,19 @@ export const getEmbeddedExplorerHTML = (
     graphRef: config.graphRef,
     target: '#embeddableExplorer',
     initialState: {
-      document: config.document,
-      headers: config.headers,
-      variables: config.variables,
+      ...('document' in config || 'headers' in config || 'variables' in config
+        ? {
+            document: config.document,
+            headers: config.headers,
+            variables: config.variables,
+          }
+        : {}),
+      ...('collectionId' in config
+        ? {
+            collectionId: config.collectionId,
+            operationId: config.operationId,
+          }
+        : {}),
       displayOptions: {
         ...productionLandingPageConfigOrDefault.displayOptions,
       },
@@ -104,9 +116,15 @@ id="embeddableExplorer"
 
 export const getEmbeddedSandboxHTML = (
   sandboxCdnVersion: string,
-  config: LandingPageConfig,
+  config: ApolloServerPluginEmbeddedLandingPageLocalDefaultOptions,
   apolloServerVersion: string,
 ) => {
+  const endpointIsEditable =
+    typeof config.embed === 'boolean'
+      ? false
+      : typeof config.embed?.endpointIsEditable === 'boolean'
+      ? config.embed?.endpointIsEditable
+      : false;
   return `
 <div class="fallback">
   <h1>Welcome to Apollo Server</h1>
@@ -132,12 +150,36 @@ id="embeddableSandbox"
     target: '#embeddableSandbox',
     initialEndpoint,
     initialState: ${getConfigStringForHtml({
-      document: config.document,
-      variables: config.variables,
-      headers: config.headers,
+      ...('document' in config || 'headers' in config || 'variables' in config
+        ? {
+            document: config.document,
+            variables: config.variables,
+            headers: config.headers,
+          }
+        : {}),
+      ...('collectionId' in config
+        ? {
+            collectionId: config.collectionId,
+            operationId: config.operationId,
+          }
+        : {}),
       includeCookies: config.includeCookies,
+      ...(typeof config.embed !== 'boolean' &&
+      config.embed?.initialState?.pollForSchemaUpdates !== undefined
+        ? {
+            pollForSchemaUpdates:
+              config.embed?.initialState?.pollForSchemaUpdates,
+          }
+        : {}),
+      ...(typeof config.embed !== 'boolean' &&
+      config.embed?.initialState?.sharedHeaders !== undefined
+        ? {
+            sharedHeaders: config.embed?.initialState?.sharedHeaders,
+          }
+        : {}),
     })},
     hideCookieToggle: false,
+    endpointIsEditable: ${endpointIsEditable},
     runtime: '${apolloServerVersion}'
   });
 </script>
diff --git a/packages/server/src/plugin/landingPage/default/index.ts b/packages/server/src/plugin/landingPage/default/index.ts
index 3b5af1e10..7f607870a 100644
--- a/packages/server/src/plugin/landingPage/default/index.ts
+++ b/packages/server/src/plugin/landingPage/default/index.ts
@@ -136,7 +136,9 @@ function ApolloServerPluginLandingPageDefault<TContext extends BaseContext>(
       config.embed
         ? 'graphRef' in config && config.graphRef
           ? getEmbeddedExplorerHTML(version, config, apolloServerVersion)
-          : getEmbeddedSandboxHTML(version, config, apolloServerVersion)
+          : !('graphRef' in config)
+          ? getEmbeddedSandboxHTML(version, config, apolloServerVersion)
+          : getNonEmbeddedLandingPageHTML(version, config, apolloServerVersion)
         : getNonEmbeddedLandingPageHTML(version, config, apolloServerVersion)
     }
     </div>
diff --git a/packages/server/src/plugin/landingPage/default/types.ts b/packages/server/src/plugin/landingPage/default/types.ts
index dee5da345..211aef600 100644
--- a/packages/server/src/plugin/landingPage/default/types.ts
+++ b/packages/server/src/plugin/landingPage/default/types.ts
@@ -1,17 +1,4 @@
-export interface ApolloServerPluginLandingPageDefaultBaseOptions {
-  /**
-   * By default, the landing page plugin uses the latest version of the landing
-   * page published to Apollo's CDN. If you'd like to pin the current version,
-   * pass the SHA served at
-   * https://apollo-server-landing-page.cdn.apollographql.com/_latest/version.txt
-   * here.
-   */
-  version?: string;
-  /**
-   * Set to false to suppress the footer which explains how to configure the
-   * landing page.
-   */
-  footer?: boolean;
+type InitialDocumentVariablesHeaders = {
   /**
    * Users can configure their landing page to link to Studio Explorer with a
    * document loaded in the UI.
@@ -22,63 +9,128 @@ export interface ApolloServerPluginLandingPageDefaultBaseOptions {
    * variables loaded in the UI.
    */
   variables?: Record<string, any>;
+
   /**
    * Users can configure their landing page to link to Studio Explorer with
    * headers loaded in the UI.
    */
   headers?: Record<string, string>;
+  collectionId?: never;
+  operationId?: never;
+};
 
-  includeCookies?: boolean;
-  // For Apollo use only.
-  __internal_apolloStudioEnv__?: 'staging' | 'prod';
-}
-
-export interface ApolloServerPluginNonEmbeddedLandingPageLocalDefaultOptions
-  extends ApolloServerPluginLandingPageDefaultBaseOptions {
-  /**
-   * Users can configure their landing page to render an embedded Explorer if
-   * given a graphRef, or an embedded Sandbox if there is not graphRef provided.
-   */
-  embed: false;
-}
+type InitialStateForEmbeds =
+  | {
+      /**
+       * The ID of a collection, paired with an operation ID to populate in the Sandbox on load.
+       *
+       * You can find these values from a registered graph in Studio by
+       * clicking the ... menu next to an operation in the Explorer of that graph and
+       * selecting View operation details.
+       */
+      collectionId: string;
+      operationId: string;
+    }
+  | InitialDocumentVariablesHeaders;
 
-export interface ApolloServerPluginNonEmbeddedLandingPageProductionDefaultOptions
-  extends ApolloServerPluginLandingPageDefaultBaseOptions {
+export type ApolloServerPluginLandingPageDefaultBaseOptions = {
   /**
-   * If specified, provide a link (with opt-in auto-redirect) to the Studio page
-   * for the given graphRef. (You need to explicitly pass this here rather than
-   * relying on the server's ApolloConfig, because if your server is publicly
-   * accessible you may not want to display the graph ref publicly.)
+   * By default, the landing page plugin uses the latest version of the landing
+   * page published to Apollo's CDN. If you'd like to pin the current version,
+   * pass the SHA served at
+   * https://apollo-server-landing-page.cdn.apollographql.com/_latest/version.txt
+   * here.
    */
-  graphRef?: string;
+  version?: string;
   /**
-   * Users can configure their landing page to render an embedded Explorer if
-   * given a graphRef, or an embedded Sandbox if there is not graphRef provided.
+   * Set to false to suppress the footer which explains how to configure the
+   * landing page.
    */
-  embed?: false;
-}
+  footer?: boolean;
 
-export interface ApolloServerPluginEmbeddedLandingPageLocalDefaultOptions
-  extends ApolloServerPluginLandingPageDefaultBaseOptions {
-  /**
-   * Users can configure their landing page to render an embedded Explorer if
-   * given a graphRef, or an embedded Sandbox if there is not graphRef provided.
-   */
-  embed?: true;
-}
+  includeCookies?: boolean;
+  // For Apollo use only.
+  __internal_apolloStudioEnv__?: 'staging' | 'prod';
+};
 
-export interface ApolloServerPluginEmbeddedLandingPageProductionDefaultOptions
-  extends ApolloServerPluginLandingPageDefaultBaseOptions {
-  /**
-   * Use this registered's graphs schema to populate the embedded Explorer.
-   * Required if passing `embed: true`
-   */
-  graphRef: string;
+export type ApolloServerPluginNonEmbeddedLandingPageLocalDefaultOptions =
+  ApolloServerPluginLandingPageDefaultBaseOptions &
+    InitialDocumentVariablesHeaders & {
+      /**
+       * Users can configure their landing page to render an embedded Explorer if
+       * given a graphRef, or an embedded Sandbox if there is no graphRef provided.
+       */
+      embed: false;
+    };
+
+export type ApolloServerPluginNonEmbeddedLandingPageProductionDefaultOptions =
+  ApolloServerPluginLandingPageDefaultBaseOptions &
+    InitialDocumentVariablesHeaders & {
+      /**
+       * If specified, provide a link (with opt-in auto-redirect) to the Studio page
+       * for the given graphRef. (You need to explicitly pass this here rather than
+       * relying on the server's ApolloConfig, because if your server is publicly
+       * accessible you may not want to display the graph ref publicly.)
+       */
+      graphRef?: string;
+      /**
+       * Users can configure their landing page to render an embedded Explorer if
+       * given a graphRef, or an embedded Sandbox if there is no graphRef provided.
+       */
+      embed?: false;
+    };
+
+export type ApolloServerPluginEmbeddedLandingPageLocalDefaultOptions =
+  ApolloServerPluginLandingPageDefaultBaseOptions & {
+    /**
+     * Users can configure their landing page to render an embedded Explorer if
+     * given a graphRef, or an embedded Sandbox if there is no graphRef provided.
+     */
+    embed?: true | EmbeddableSandboxOptions;
+  } & (InitialDocumentVariablesHeaders | InitialStateForEmbeds);
+
+export type ApolloServerPluginEmbeddedLandingPageProductionDefaultOptions =
+  ApolloServerPluginLandingPageDefaultBaseOptions & {
+    /**
+     * Use this registered's graphs schema to populate the embedded Explorer.
+     * Required if passing `embed: true`
+     */
+    graphRef: string;
+    /**
+     * Users can configure their landing page to render an embedded Explorer.
+     */
+    embed: true | EmbeddableExplorerOptions;
+  } & InitialStateForEmbeds;
+
+type EmbeddableSandboxOptions = {
+  initialState?: {
+    /**
+     * If true, the embedded Sandbox periodically polls your initialEndpoint for schema updates.
+     *
+     * The default value is true.
+     */
+    pollForSchemaUpdates?: boolean;
+    /**
+     * Headers that are applied by default to every operation executed by the landing page's Sandbox.
+     * Users can disable the application of these headers, but they can't modify their values.
+     *
+     * The landing page's Sandbox always includes these headers in its introspection queries
+     * to your endpoint.
+     */
+    // TODO(as5): We currently have `headers` at the top level and `embed.initialState.sharedHeaders`
+    // at this level. Shared headers populate introspection headers in Sandbox, while `headers` just
+    // adds headers to your default tab. We should consider changing the behavior such that `headers`
+    // at the base level populates shared headers as well.
+    sharedHeaders?: Record<string, string>;
+  };
   /**
-   * Users can configure their landing page to render an embedded Explorer.
+   * By default, the Apollo Server embedded Sandbox has a url input box that is not editable by users.
+   *
+   * Set `endpointIsEditable` to `true` to enable users of your
+   * Apollo Server landing page to change the endpoint url.
    */
-  embed: true | EmbeddableExplorerOptions;
-}
+  endpointIsEditable?: boolean;
+};
 
 type EmbeddableExplorerOptions = {
   /**


Test patch:
diff --git a/packages/server/src/__tests__/plugin/landingPage/getEmbeddedExplorerHTML.test.ts b/packages/server/src/__tests__/plugin/landingPage/getEmbeddedExplorerHTML.test.ts
index 0682cd068..a47729b91 100644
--- a/packages/server/src/__tests__/plugin/landingPage/getEmbeddedExplorerHTML.test.ts
+++ b/packages/server/src/__tests__/plugin/landingPage/getEmbeddedExplorerHTML.test.ts
@@ -62,11 +62,92 @@ describe('Embedded Explorer Landing Page Config HTML', () => {
     `);
   });
 
+  it('with only headers provided', () => {
+    const config: ApolloServerPluginEmbeddedLandingPageProductionDefaultOptions =
+      {
+        includeCookies: true,
+        headers: { authorization: 'true' },
+        embed: true,
+        graphRef: 'graph@current',
+      };
+    expect(getEmbeddedExplorerHTML(cdnVersion, config, apolloServerVersion))
+      .toMatchInlineSnapshot(`
+      <div class="fallback">
+        <h1>
+          Welcome to Apollo Server
+        </h1>
+        <p>
+          Apollo Explorer cannot be loaded; it appears that you might be offline.
+        </p>
+      </div>
+      <style>
+        iframe {
+          background-color: white;
+        }
+      </style>
+      <div style="width: 100vw; height: 100vh; position: absolute; top: 0;"
+           id="embeddableExplorer"
+      >
+      </div>
+      <script src="https://embeddable-explorer.cdn.apollographql.com/_latest/embeddable-explorer.umd.production.min.js?runtime=%40apollo%2Fserver%404.0.0">
+      </script>
+      <script>
+        var endpointUrl = window.location.href;
+        var embeddedExplorerConfig = {"graphRef":"graph@current","target":"#embeddableExplorer","initialState":{"headers":{"authorization":"true"},"displayOptions":{}},"persistExplorerState":false,"includeCookies":true,"runtime":"@apollo/server@4.0.0"};
+        new window.EmbeddedExplorer({
+          ...embeddedExplorerConfig,
+          endpointUrl,
+        });
+      </script>
+    `);
+  });
+
+  it('with operationId, collectionId provided', () => {
+    const config: ApolloServerPluginEmbeddedLandingPageProductionDefaultOptions =
+      {
+        includeCookies: true,
+        collectionId: '12345',
+        operationId: 'abcdef',
+        embed: true,
+        graphRef: 'graph@current',
+      };
+    expect(getEmbeddedExplorerHTML(cdnVersion, config, apolloServerVersion))
+      .toMatchInlineSnapshot(`
+      <div class="fallback">
+        <h1>
+          Welcome to Apollo Server
+        </h1>
+        <p>
+          Apollo Explorer cannot be loaded; it appears that you might be offline.
+        </p>
+      </div>
+      <style>
+        iframe {
+          background-color: white;
+        }
+      </style>
+      <div style="width: 100vw; height: 100vh; position: absolute; top: 0;"
+           id="embeddableExplorer"
+      >
+      </div>
+      <script src="https://embeddable-explorer.cdn.apollographql.com/_latest/embeddable-explorer.umd.production.min.js?runtime=%40apollo%2Fserver%404.0.0">
+      </script>
+      <script>
+        var endpointUrl = window.location.href;
+        var embeddedExplorerConfig = {"graphRef":"graph@current","target":"#embeddableExplorer","initialState":{"collectionId":"12345","operationId":"abcdef","displayOptions":{}},"persistExplorerState":false,"includeCookies":true,"runtime":"@apollo/server@4.0.0"};
+        new window.EmbeddedExplorer({
+          ...embeddedExplorerConfig,
+          endpointUrl,
+        });
+      </script>
+    `);
+  });
+
   it('for embedded explorer with document, variables, headers and displayOptions excluded', () => {
     const config: ApolloServerPluginEmbeddedLandingPageProductionDefaultOptions =
       {
         includeCookies: false,
-        embed: true as true,
+        embed: true,
         graphRef: 'graph@current',
       };
     expect(getEmbeddedExplorerHTML(cdnVersion, config, apolloServerVersion))
diff --git a/packages/server/src/__tests__/plugin/landingPage/getEmbeddedSandboxHTML.test.ts b/packages/server/src/__tests__/plugin/landingPage/getEmbeddedSandboxHTML.test.ts
index 8ad9f1021..bfc7ae3c2 100644
--- a/packages/server/src/__tests__/plugin/landingPage/getEmbeddedSandboxHTML.test.ts
+++ b/packages/server/src/__tests__/plugin/landingPage/getEmbeddedSandboxHTML.test.ts
@@ -1,5 +1,5 @@
 import { getEmbeddedSandboxHTML } from '../../../plugin/landingPage/default/getEmbeddedHTML';
-import type { LandingPageConfig } from '../../../plugin/landingPage/default/types';
+import type { ApolloServerPluginEmbeddedLandingPageLocalDefaultOptions } from '../../../plugin/landingPage/default/types';
 import { describe, it, expect } from '@jest/globals';
 
 const cdnVersion = '_latest';
@@ -8,7 +8,7 @@ const apolloServerVersion = '@apollo/server@4.0.0';
 
 describe('Landing Page Config HTML', () => {
   it('for embedded sandbox with document, variables and headers provided', () => {
-    const config: LandingPageConfig = {
+    const config: ApolloServerPluginEmbeddedLandingPageLocalDefaultOptions = {
       includeCookies: true,
       document: 'query Test { id }',
       variables: {
@@ -49,15 +49,97 @@ describe('Landing Page Config HTML', () => {
           initialEndpoint,
           initialState: {"document":"query Test { id }","variables":{"option":{"a":"val","b":1,"c":true}},"headers":{"authorization":"true"},"includeCookies":true},
           hideCookieToggle: false,
+          endpointIsEditable: false,
           runtime: '@apollo/server@4.0.0'
         });
       </script>
     `);
   });
 
-  it('for embedded sandbox with document, variables and headers excluded', () => {
-    const config: LandingPageConfig = {
-      includeCookies: false,
+  it('for embedded sandbox with only headers provided', () => {
+    const config: ApolloServerPluginEmbeddedLandingPageLocalDefaultOptions = {
+      includeCookies: true,
+      headers: { authorization: 'true' },
+      embed: true,
+    };
+    expect(getEmbeddedSandboxHTML(cdnVersion, config, apolloServerVersion))
+      .toMatchInlineSnapshot(`
+      <div class="fallback">
+        <h1>
+          Welcome to Apollo Server
+        </h1>
+        <p>
+          Apollo Sandbox cannot be loaded; it appears that you might be offline.
+        </p>
+      </div>
+      <style>
+        iframe {
+          background-color: white;
+        }
+      </style>
+      <div style="width: 100vw; height: 100vh; position: absolute; top: 0;"
+           id="embeddableSandbox"
+      >
+      </div>
+      <script src="https://embeddable-sandbox.cdn.apollographql.com/_latest/embeddable-sandbox.umd.production.min.js?runtime=%40apollo%2Fserver%404.0.0">
+      </script>
+      <script>
+        var initialEndpoint = window.location.href;
+        new window.EmbeddedSandbox({
+          target: '#embeddableSandbox',
+          initialEndpoint,
+          initialState: {"headers":{"authorization":"true"},"includeCookies":true},
+          hideCookieToggle: false,
+          endpointIsEditable: false,
+          runtime: '@apollo/server@4.0.0'
+        });
+      </script>
+    `);
+  });
+
+  it('for embedded sandbox with all config excluded', () => {
+    const config: ApolloServerPluginEmbeddedLandingPageLocalDefaultOptions = {
+      embed: true,
+    };
+    expect(getEmbeddedSandboxHTML(cdnVersion, config, apolloServerVersion))
+      .toMatchInlineSnapshot(`
+      <div class="fallback">
+        <h1>
+          Welcome to Apollo Server
+        </h1>
+        <p>
+          Apollo Sandbox cannot be loaded; it appears that you might be offline.
+        </p>
+      </div>
+      <style>
+        iframe {
+          background-color: white;
+        }
+      </style>
+      <div style="width: 100vw; height: 100vh; position: absolute; top: 0;"
+           id="embeddableSandbox"
+      >
+      </div>
+      <script src="https://embeddable-sandbox.cdn.apollographql.com/_latest/embeddable-sandbox.umd.production.min.js?runtime=%40apollo%2Fserver%404.0.0">
+      </script>
+      <script>
+        var initialEndpoint = window.location.href;
+        new window.EmbeddedSandbox({
+          target: '#embeddableSandbox',
+          initialEndpoint,
+          initialState: {},
+          hideCookieToggle: false,
+          endpointIsEditable: false,
+          runtime: '@apollo/server@4.0.0'
+        });
+      </script>
+    `);
+  });
+
+  it('for embedded sandbox with operationId & collectionId provided', () => {
+    const config: ApolloServerPluginEmbeddedLandingPageLocalDefaultOptions = {
+      collectionId: '12345',
+      operationId: 'abcdef',
       embed: true,
     };
     expect(getEmbeddedSandboxHTML(cdnVersion, config, apolloServerVersion))
@@ -86,8 +168,55 @@ describe('Landing Page Config HTML', () => {
         new window.EmbeddedSandbox({
           target: '#embeddableSandbox',
           initialEndpoint,
-          initialState: {"includeCookies":false},
+          initialState: {"collectionId":"12345","operationId":"abcdef"},
+          hideCookieToggle: false,
+          endpointIsEditable: false,
+          runtime: '@apollo/server@4.0.0'
+        });
+      </script>
+    `);
+  });
+
+  it('for embedded sandbox with endpointIsEditable, pollForSchemaUpdates, sharedHeaders provided', () => {
+    const config: ApolloServerPluginEmbeddedLandingPageLocalDefaultOptions = {
+      includeCookies: false,
+      embed: {
+        endpointIsEditable: true,
+        initialState: {
+          pollForSchemaUpdates: false,
+          sharedHeaders: { SharedHeaderKey: 'SharedHeaderValue' },
+        },
+      },
+    };
+    expect(getEmbeddedSandboxHTML(cdnVersion, config, apolloServerVersion))
+      .toMatchInlineSnapshot(`
+      <div class="fallback">
+        <h1>
+          Welcome to Apollo Server
+        </h1>
+        <p>
+          Apollo Sandbox cannot be loaded; it appears that you might be offline.
+        </p>
+      </div>
+      <style>
+        iframe {
+          background-color: white;
+        }
+      </style>
+      <div style="width: 100vw; height: 100vh; position: absolute; top: 0;"
+           id="embeddableSandbox"
+      >
+      </div>
+      <script src="https://embeddable-sandbox.cdn.apollographql.com/_latest/embeddable-sandbox.umd.production.min.js?runtime=%40apollo%2Fserver%404.0.0">
+      </script>
+      <script>
+        var initialEndpoint = window.location.href;
+        new window.EmbeddedSandbox({
+          target: '#embeddableSandbox',
+          initialEndpoint,
+          initialState: {"includeCookies":false,"pollForSchemaUpdates":false,"sharedHeaders":{"SharedHeaderKey":"SharedHeaderValue"}},
           hideCookieToggle: false,
+          endpointIsEditable: true,
           runtime: '@apollo/server@4.0.0'
         });
       </script>