Repository: newrelic/newrelic-node-apollo-server-plugin
Pull request #47 (2020-10-13 22:17:41)

Adds detailed documentation.
<!--
Thank you for submitting a Pull Request.

This code is leveraged to monitor critical services. Please consider the following:
* Tests are required.
* Performance matters.
* Features that are specific to just your app are unlikely to make it in.

Please fill out the relevant sections as follows:
* Proposed Release Notes: Bulleted list of recommended release notes for the change(s).
* Links: Any relevant links for the change.
* Details: In-depth description of changes, other technical notes, etc.
-->

## Proposed Release Notes

## Links

* Closes: https://github.com/newrelic/newrelic-node-apollo-server-plugin/issues/5

## Details

Adds detailed documentation including the new configuration and details on how we are naming things, metrics and how to visualize those.

Approach attempted is surfacing up some details on the README and then pointing to specific markdown pages for more in-depth details.


Patch:
diff --git a/README.md b/README.md
index d30e712..cc2f6f1 100644
--- a/README.md
+++ b/README.md
@@ -4,9 +4,12 @@
 
 New Relic's official Apollo Server plugin for use with the [Node.js agent](https://github.com/newrelic/node-newrelic).
 
-## Installation and getting started
+This plugin expects the Node.js agent [newrelic npm package](https://www.npmjs.com/package/newrelic) has already been installed in your application.
+
+## Installation
 
 ```
+npm install newrelic
 npm install @newrelic/apollo-server-plugin
 ```
 
@@ -22,6 +25,41 @@ const server = new ApolloServer({
 })
 ```
 
+## Getting started
+
+The `@newrelic/apollo-server-plugin` exports a `createPlugin` function that does accept limited configuration. Since Apollo Server will invoke any function passed to plugins, invoking this yourself is not required unless you plan to override specific configuration.
+
+To use the plugin without specific configuration, you can pass the function in directly similar to:
+
+```js
+// index.js
+const plugin = require('@newrelic/apollo-server-plugin')
+
+// imported from supported module
+const server = new ApolloServer({
+  typeDefs,
+  resolvers,
+  plugins: [plugin]
+})
+```
+
+To override configuration, invoke the `createPlugin` function prior to passing to Apollo Server:
+
+```js
+// index.js
+const createPlugin = require('@newrelic/apollo-server-plugin')
+const plugin = createPlugin({
+  captureScalars: false
+})
+
+// imported from supported module
+const server = new ApolloServer({
+  typeDefs,
+  resolvers,
+  plugins: [plugin]
+})
+```
+
 ## Usage
 
 The New Relic plugin is known to work with the following Apollo Server modules:
@@ -31,7 +69,116 @@ The New Relic plugin is known to work with the following Apollo Server modules:
 * apollo-server-koa
 * apollo-server-fastify
 
-Note:  Because fastify is not fully instrumented in the Node Agent, transactions will be prefixed with `WebTransaction\Nodejs`.
+Note: Because fastify is not fully instrumented in the Node.js Agent, transactions will be prefixed with `WebTransaction\Nodejs`.
+
+Other plugins may work, depending on their underlying implementation, but have not been verified.
+
+### Configuration
+
+Configuration may be passed into the `createPlugin` function to override specific values. The configuration object and all properties are optional.
+
+```js
+const plugin = createPlugin({
+  captureScalars: true
+})
+```
+
+* `[captureScalars = false]` Enable capture of timing of fields resolved with the
+  `GraphQLScalarType` return type. This may be desired when performing time intensive
+  calculations to return a scalar value. This is not recommended for queries that return
+  a large number of pre-calculated scalar fields.
+
+  **NOTE:** query/mutation resolvers will always be captured even if returning a scalar type.
+
+### Transactions
+
+[Transaction Documentation](./docs/transactions.md)
+
+Transactions are captured as web transactions, associated with the underlying framework (Express, Koa, etc.), and named based on the GraphQL operations executed.
+
+Here's an example query and how that may be represented in NR One.
+
+```
+query {
+  libraries {
+    books {
+      title
+      author {
+        name
+      }
+    }
+  }
+}
+```
+
+`post /query/<anonymous>/libraries.books.author.name`
+
+For more information on how transactions are named, including how query errors may impact naming, please see the [transaction documentation](./docs/transactions.md).
+
+### Metrics
+
+[Metrics Documentation](./docs/metrics.md)
+
+Two new metrics have been introduced to understand the behavior of your GraphQL operations within and across transactions.
+
+#### Operation Metrics
+
+`/GraphQL/operation/ApolloServer/[operation-type]/[operation-name]/[deepest-path]`
+
+Operation metrics are very similar to how transaction names are constructed including the operation type, operation name and deepest-path. These metrics represent the durations of the individual queries or mutations and can be used to compare outside of the context of individual transactions which may have multiple queries.
+
+If you would like to have a list of the top 10 slowest operations, the following query can be used to pull the data on demand or as a part of a dashboard. The 'Bar' chart type is a recommended visualization for this query.
+
+```
+FROM Metric SELECT average(newrelic.timeslice.value) * 1000 WHERE appName = '[YOUR APP NAME]' WITH METRIC_FORMAT 'GraphQL/operation/ApolloServer/{operation}' FACET operation LIMIT 10
+```
+
+#### Field Resolve Metrics
+
+`/GraphQL/resolve/ApolloServer/[field-name]`
+
+Resolve metrics capture the duration spent resolving a particular piece of requested GraphQL data. These can be useful to find specific resolvers that may contribute to slowing down incoming queries.
+
+If you would like to have a list of the top 10 slowest resolves, the following query can be used to pull the data on demand or as a part of a dashboard. The 'Bar' chart type is a recommended visualization for this query.
+
+```
+FROM Metric
+SELECT average(newrelic.timeslice.value) * 1000 WHERE appName = '[YOUR APP NAME]' WITH METRIC_FORMAT 'GraphQL/resolve/ApolloServer/{field}' FACET field LIMIT 10
+```
+
+For more information on metrics and some recommended visualizations, please see the [metrics documentation](./docs/metrics.md).
+
+### Segments and Spans
+
+[Segments and Spans](./docs/segments-and-spans.md)
+
+Segments and spans (when Distributed Tracing enabled) are captured for GraphQL operations, field resolution and additional work (when instrumented) that occurs as a part of field resolution such as making a query to a database.
+
+#### Operation Segments/Spans
+
+`/GraphQL/operation/ApolloServer/[operation-type]/[operation-name]/[deepest-path]`
+
+Operation segments/spans include the operation type, operation name and deepest-path. These represent the individual duration and attributes of a specific invocation within a transaction or trace.
+
+The operation type, operation name and deepest-path are captured as attributes on a segment or span as well as the query with obfuscated arguments.
+
+For more information on collected attributes, see the [segments and spans documentation](./docs/segments-and-spans.md)
+
+#### Field Resolve Segments/Spans
+
+`/GraphQL/resolve/ApolloServer/[path]`
+
+Resolve segments/spans leverage the resolution path of the individual field to best differentiate within a given trace or transaction. For example, `libraries.books` might be used instead of just `books`. These represent the individual duration and attributes of a specific field being resolved as a part of the GraphQL operation.
+
+The field name, return type, parent type, path and arguments (disabled by default) are all captured as attributes.
+
+For more information on collected attributes, including enabling args capture, see the [segments and spans documentation](./docs/segments-and-spans.md)
+
+### Errors
+
+The agent will notice GraphQL errors that get sent back to the client.
+
+Depend on where the error was thrown, these will be associated with either the operation span or the specific field resolve span (Distribute Tracing enabled) to enable further debugging of issues.
 
 ## Testing
 
diff --git a/docs/metrics.md b/docs/metrics.md
new file mode 100644
index 0000000..d0c30a2
--- /dev/null
+++ b/docs/metrics.md
@@ -0,0 +1,83 @@
+# Metrics
+
+Two new metrics have been introduced to understand the behavior of your GraphQL operations within and across transactions. Read more on those below or jump down to the [Visualizations](#visualizations) section to see some recommended ways to use this data.
+
+For more information on querying metrics and creating charts, see the [Resources](#resources) section.
+
+## Operation Metrics
+
+`/GraphQL/operation/ApolloServer/[operation-type]/[operation-name]/[deepest-path]`
+
+Operation metrics include the operation type, operation name and deepest-path. These metrics represent the durations of the individual queries or mutations and can be used to compare outside of the context of individual transactions which may have multiple queries.
+
+**Operation Type:** Indicates if the operation was a query or a mutation.
+
+**Operation Name:** The operation name when provided or `<anonymous>`.
+
+**Deepest Path:** The deepest path resolved (or attempted in the case of a validation error). Since operation names may be reused, this helps further determine uniqueness of a given operation.
+
+## Field Resolve Metrics
+
+`/GraphQL/resolve/ApolloServer/[field-name]`
+
+Resolve metrics capture the duration spent resolving a particular piece of requested GraphQL data. These can be useful to find specific resolvers that may contribute to slowing down incoming queries.
+
+These differ slightly in naming from their segment/span counterparts. Be better visualize relationships, the full path to a field is represented in segments/spans (i.e. libraries.books.title). To understand the duration aggregated across all usages and transactions, the metric uses the field name without the full path.
+
+## Visualizations
+
+Here is a collection of useful queries that leverage these new metrics to better understand the behaviors of your Apollo GraphQL applications.
+
+### Top 10 Operations
+
+If you would like to have a list of the top 10 slowest operations, the following query can be used to pull the data on demand or as a part of a dashboard.
+
+```
+FROM Metric SELECT average(newrelic.timeslice.value) * 1000 WHERE appName = '[YOUR APP NAME]' WITH METRIC_FORMAT 'GraphQL/operation/ApolloServer/{operation}' FACET operation LIMIT 10
+```
+
+The 'Bar' chart type makes a nice visualization similar to the transaction overview you may be used to.
+
+A 'Table' chart type may also be useful showing a breakdown of operations. For this scenario, we recommend leveraging the METRIC_FORMAT to give further sorting and visualization flexibility. The following query will generate columns of operation type, operation name, deepest-path and 'AVG Duration (MS)' to sort and examine as you wish.
+
+```
+FROM Metric SELECT average(newrelic.timeslice.value) * 1000 as 'AVG Duration (MS)' WHERE appName = '[YOUR APP NAME]' WITH METRIC_FORMAT 'GraphQL/operation/ApolloServer/{type}/{name}/{deepest-path}' FACET type, name, `deepest-path` LIMIT 20
+```
+
+### Average Operation Time
+
+You may also wish to track the average duration over time for operations. To do this, a very similar query may be used leveraging `TIMESERIES`.
+
+```
+FROM Metric SELECT average(newrelic.timeslice.value) WHERE appName = '[YOUR APP NAME]' WITH METRIC_FORMAT 'GraphQL/operation/ApolloServer/{operation}' TIMESERIES FACET operation
+```
+
+This is best viewed with the 'Line' chart type which allows for viewing all operations or toggling visualization of individual operations.
+
+### Top 10 Resolvers
+
+If you would like to have a list of the top 10 slowest resolves, the following query can be used to pull the data on demand or as a part of a dashboard.
+
+```
+FROM Metric
+SELECT average(newrelic.timeslice.value) * 1000 as 'Average Duration (MS)' WHERE appName = '[YOUR APP NAME]' WITH METRIC_FORMAT 'GraphQL/resolve/ApolloServer/{field}' FACET field LIMIT 20
+```
+
+The 'Bar' chart type makes a nice visualization similar to the transaction overview you may be used to. The 'Table' chart type may also be useful showing a breakdown of field and 'Average Duration (MS)' in a table.
+
+### Average Resolver Time
+
+You may also wish to track the average duration over time for resolvers. To do this, a very similar query may be used leveraging `TIMESERIES`.
+
+```
+FROM Metric
+SELECT average(newrelic.timeslice.value) * 1000 as 'Average Duration (MS)' TIMESERIES WHERE appName = '[YOUR APP NAME]' WITH METRIC_FORMAT 'GraphQL/resolve/ApolloServer/{field}' FACET field
+```
+
+This is best viewed with the 'Line' chart type which allows for viewing all operations or toggling visualization of individual operations.
+
+## Resources
+
+* [Query APM metric timeslice data with NRQL](https://docs.newrelic.com/docs/query-your-data/nrql-new-relic-query-language/nrql-query-tutorials/query-apm-metric-timeslice-data-nrql)
+
+* [Add and customize metric charts](https://docs.newrelic.com/docs/insights/use-insights-ui/manage-dashboards/add-customize-metric-charts)
diff --git a/docs/segments-and-spans.md b/docs/segments-and-spans.md
new file mode 100644
index 0000000..24263ad
--- /dev/null
+++ b/docs/segments-and-spans.md
@@ -0,0 +1,41 @@
+# Segments and Spans
+
+Segments and spans (when Distributed Tracing enabled) are captured for GraphQL operations, field resolution and additional work (when instrumented) that occurs as a part of field resolution such as making a query to a database.
+
+## Operation Segments/Spans
+
+`/GraphQL/operation/ApolloServer/[operation-type]/[operation-name]/[deepest-path]`
+
+Operation segments/spans include the operation type, operation name and deepest-path. These represent the individual duration and attributes of a specific invocation within a transaction or trace.
+
+**Attributes**
+
+| Name                   | Description      | Default  |
+| ---------------------- | ---------------- | -------- |
+| graphql.operation.type | query or mutation| included |
+| graphql.operation.name | Name given to the operation or anonymous | included |
+| graphql.operation.deepestPath | Deepest path resolved as the part of an operation | included |
+| graphql.operation.query | The original GraphQL query with arguments obfuscated | included |
+
+To exclude capture of the query attribute (or any attribute), the attribute name will need to be added to the 'attributes' exclude list or segment/span attributes exclude lists individually.
+
+For more information on including/excluding attributes, please see the [attributes documentation](https://docs.newrelic.com/docs/agents/nodejs-agent/attributes/nodejs-agent-attributes#configure-attributes).
+
+## Field Resolve Segments/Spans
+
+`/GraphQL/resolve/ApolloServer/[path]`
+
+Resolve segments/spans leverage the resolution path of the individual field to best differentiate within a given trace or transaction. For example, `libraries.books` might be used instead of just `books`. These represent the individual duration and attributes of a specific field being resolved as a part of the GraphQL operation.
+
+**Attributes**
+| Name                     | Description                | Default  |
+| ------------------------ | -------------------------- | -------- |
+| graphql.field.name       | Name of the resolved field | included |
+| graphql.field.returnType | Return type (`Book!`, `[String]`, etc. ) of the resolved field | included |
+| graphql.field.parentType | Type of the parent of this field (`[Book]`) | included |
+| graphql.field.path | Full resolve path of the field (`libraries.books`) | included |
+| graphql.field.args | Arg passed to the GraphQL query for this resolver captured as key/value pairs | excluded |
+
+To include capture of args attributes, `graphql.field.args.*` (to capture all) will need to be added to the 'attributes' include list or segment/span attributes include lists individually.
+
+For more information on including/excluding attributes, please see the [attributes documentation](https://docs.newrelic.com/docs/agents/nodejs-agent/attributes/nodejs-agent-attributes#configure-attributes).
\ No newline at end of file
diff --git a/docs/transactions.md b/docs/transactions.md
new file mode 100644
index 0000000..73addcd
--- /dev/null
+++ b/docs/transactions.md
@@ -0,0 +1,122 @@
+# Transactions
+
+```
+query {
+  libraries {
+    books {
+      title
+      author {
+        name
+      }
+    }
+  }
+}
+```
+
+`post /query/<anonymous>/libraries.books.author.name`
+
+---
+
+Transactions are captured as web transactions, associated with the underlying framework (Express, Koa, etc.), and named based on the GraphQL operations executed.
+
+We leverage several details in a transaction name to attempt to mostly group unique query representations: http method, operation type, operation name and the deepest path resolved (the first, if multiple).
+
+The raw representation of a transaction looks like the following: `/WebTransaction/{framework-name}/POST//{operation-type}/{operation-name}/{deepest-path}`
+
+For an Express usage of Apollo Server, that may look like: `/WebTransaction/Expressjs/POST//query/<anonymous>/libraries.books.author.name`
+
+The transaction on New Relic One will ultimately display similar to: `post /query/<anonymous>/libraries.books.author.name`.
+
+## Details
+
+**Http Method:** Http method/verb for the web request. Data may be requested via GET or POST and is surfaced to differentiate similar to other web transactions.
+
+**Operation Type:** Indicates if the operation was a query or a mutation.
+
+**Operation Name:** The operation name when provided or `<anonymous>`.
+
+**Deepest Path:** The deepest path resolved (or attempted in the case of a validation error). Since operation names may be reused, this helps further determine uniqueness of a given operation.
+
+## Naming on Error
+
+Errors parsing or validating a GraphQL request can impact transaction naming.
+
+### Validation Errors
+
+If a request was able to parse, but was not able to validate, we will name the transaction off what was attempted to be queried. For example: when a field in the incoming GraphQL query does not exist.
+
+In this situation, we'll leverage the parsed document to indicate each of the intended pieces including calculating the deepest path intended.
+
+Below is an example of querying for a field that does not exist (`doesnotexist`) and what that may look like in NR One.
+
+```
+query GetBooksByLibrary {
+  libraries {
+    books {
+      title
+      doesnotexist {
+        name
+      }
+    }
+  }
+}
+```
+
+`post /query/GetBooksByLibrary/libraries.books.doesnotexist.name`
+
+### Parsing Errors
+
+If a requested operation cannot be parsed, we will name the transaction using a wildcard (*) in place of the usual operation pieces. In this situation, the query is invalid and we do not know if we have any tangible pieces to safely go off of.
+
+Below is an example missing a closing `}` that cannot parse and what that may look like in NR One.
+
+```
+query GetBooksByLibrary {
+  libraries {
+    books {
+      title
+      author {
+        name
+      }
+    }
+  }
+// missing closing }
+```
+
+`post /*`
+
+In these situations, the `query` attribute on the operation span associated with the error is the best way to identify the particular offender.
+
+### Batch Queries
+
+Apollo Server allows the sending of batch queries. In these situations, there are multiple operation/queries in play to impact naming.
+
+To continue to best uniquely identify transaction groupings, we aggregate the operation names after an additional `/batch` indicator. These names are likely to be quite long. We are considering dropping the deepest-path from these names but are currently maintaining consistency.
+
+Below is an example of a batch query and what that may look like in NR One.
+
+```
+[
+  {
+    query: query GetBookForLibrary {
+      library(branch: "downtown") {
+        books {
+          title
+          author {
+            name
+          }
+        }
+      }
+    }
+  },
+  {
+    query: mutation {
+      addThing(name: "added thing!")
+    }
+  }
+]
+```
+
+`post /batch/query/GetBookForLibrary/library.books.author.name/mutation/<anonymous>/addThing`
+
+Here you see `batch/` followed by `query/GetBookForLibrary/library.books.author.name` and `mutation/<anonymous>/addThing`.
\ No newline at end of file
diff --git a/index.js b/index.js
index cb6eaff..df009f2 100644
--- a/index.js
+++ b/index.js
@@ -10,4 +10,4 @@ const createPlugin = require('./lib/create-plugin')
 
 // TODO: need to grab instrumentation API from agent via
 // supported means that will not dissapear when agent disabled.
-module.exports = createPlugin.bind(null, newrelic.shim)
+module.exports = createPlugin(newrelic.shim)
diff --git a/lib/create-plugin.js b/lib/create-plugin.js
index 8a297e6..90c1303 100644
--- a/lib/create-plugin.js
+++ b/lib/create-plugin.js
@@ -34,41 +34,15 @@ const DESTINATIONS = {
   NONE: 0x00
 }
 
-/**
- * @typedef {object} PluginConfig
- * @property {boolean} [captureScalars=false]
- * Enable capture of timing of fields resolved with the `GraphQLScalarType` return type.
- * This may be desired when performing time intensive calculations to return a scalar
- * value.This is not recommended for queries that return a large number of pre-calculated
- * scalar fields.
- * NOTE: query/mutation resolvers will always be captured even if returning a scalar type.
- */
-
-/**
- * Creates an Apollo Server plugin for capturing timing data
- * via the New Relic Node.js agent.
- * @param {*} instrumentationApi New Relic instrumentation API
- * @param {PluginConfig} [config]
- */
-function createPlugin(instrumentationApi, config = {}) {
+function createPlugin(instrumentationApi) {
   if (!instrumentationApi) {
     return {}
   }
 
-  const logger = instrumentationApi.logger.child({component: 'ApolloServerPlugin'})
-
-  logger.info('Apollo Server plugin created.')
-
-  config.captureScalars = config.captureScalars || false
-
-  logger.debug('Plugin configuration: ', config)
-
   createModuleUsageMetric(instrumentationApi.agent)
 
   return {
     requestDidStart(requestContext) {
-      logger.trace('Begin requestDidStart')
-
       const requestParent = instrumentationApi.getActiveSegment()
 
       // We do not set to active here as batched queries will hit this
@@ -100,18 +74,6 @@ function createPlugin(instrumentationApi, config = {}) {
         executionDidStart() {
           return {
             willResolveField({args, info}) {
-              const pathArray = flattenToArray(info.path)
-              const formattedPath = pathArray.reverse().join('.')
-
-              if (pathArray.length > deepestResolvedPath.depth) {
-                deepestResolvedPath.depth = pathArray.length
-                deepestResolvedPath.formatted = formattedPath
-              }
-
-              if (!config.captureScalars && !isTopLevelField(info) && isScalar(info)) {
-                return null
-              }
-
               const currentSeg = instrumentationApi.getActiveSegment()
 
               // Nest everything under operation as resolvers start/finish
@@ -126,8 +88,15 @@ function createPlugin(instrumentationApi, config = {}) {
               resolverSegment.start()
               instrumentationApi.setActiveSegment(resolverSegment)
 
+              const pathArray = flattenToArray(info.path)
+              const formattedPath = pathArray.reverse().join('.')
+
               resolverSegment.name = `${RESOLVE_PREFIX}/${formattedPath}`
 
+              if (pathArray.length > deepestResolvedPath.depth) {
+                deepestResolvedPath.depth = pathArray.length
+                deepestResolvedPath.formatted = formattedPath
+              }
               resolverSegment.addAttribute(FIELD_PATH_ATTR, formattedPath)
               resolverSegment.addAttribute(FIELD_NAME_ATTR, info.fieldName)
               resolverSegment.addAttribute(RETURN_TYPE_ATTR, info.returnType.toString())
@@ -203,8 +172,6 @@ function createPlugin(instrumentationApi, config = {}) {
 
           operationSegment.name = `${OPERATION_PREFIX}/${segmentName}`
           operationSegment.end()
-
-          logger.trace('End willSendResponse')
         }
       }
     }
@@ -229,36 +196,6 @@ function getOperationDetails(responseContext, deepestPath) {
   return null
 }
 
-function isScalar(fieldInfo) {
-  const result =
-    isScalarType(fieldInfo.returnType) || isNonNullScalarType(fieldInfo.returnType)
-  return result
-}
-
-function isScalarType(typeInstance) {
-  const typeName = typeInstance.constructor.name
-  const result = typeName === 'GraphQLScalarType'
-  return result
-}
-
-function isNonNullScalarType(returnType) {
-  const returnTypeName = returnType.constructor.name
-  if (returnTypeName !== 'GraphQLNonNull' || !returnType.ofType) {
-    return false
-  }
-
-  const nestedType = returnType.ofType
-  const result = isScalarType(nestedType)
-
-  return result
-}
-
-function isTopLevelField(fieldInfo) {
-  const parentName = fieldInfo.parentType.name
-  const result = parentName === 'Query' || parentName === 'Mutation'
-  return result
-}
-
 function getDetailsFromDocument(document) {
   const definition = document.definitions[0]
 


Test patch:
diff --git a/tests/apollo-server-setup.js b/tests/apollo-server-setup.js
index 5fc0648..9cb8537 100644
--- a/tests/apollo-server-setup.js
+++ b/tests/apollo-server-setup.js
@@ -14,7 +14,7 @@ const { getTypeDefs, resolvers } = require('./data-definitions')
 
 const WEB_FRAMEWORK = 'Expressjs'
 
-function setupApolloServerTests({suiteName, createTests, pluginConfig}, agentConfig) {
+function setupApolloServerTests({suiteName, createTests}, config) {
   tap.test(`apollo-server: ${suiteName}`, (t) => {
     t.autoend()
 
@@ -24,11 +24,11 @@ function setupApolloServerTests({suiteName, createTests, pluginConfig}, agentCon
 
     t.beforeEach((done) => {
       // load default instrumentation. express being critical
-      helper = utils.TestAgent.makeInstrumented(agentConfig)
+      helper = utils.TestAgent.makeInstrumented(config)
       const createPlugin = require('../lib/create-plugin')
       const nrApi = helper.getAgentApi()
 
-      const plugin = createPlugin(nrApi.shim, pluginConfig)
+      const plugin = createPlugin(nrApi.shim)
 
       // Do after instrumentation to ensure express isn't loaded too soon.
       const { ApolloServer, gql } = require('apollo-server')
@@ -38,7 +38,7 @@ function setupApolloServerTests({suiteName, createTests, pluginConfig}, agentCon
         plugins: [plugin]
       })
 
-      server.listen({port: 0}).then(({ url }) => {
+      server.listen().then(({ url }) => {
         serverUrl = url
 
         t.context.helper = helper
diff --git a/tests/data-definitions.js b/tests/data-definitions.js
index eb9a8b6..bf7704f 100644
--- a/tests/data-definitions.js
+++ b/tests/data-definitions.js
@@ -26,31 +26,6 @@ const books = [
     isbn: 'a-second-fake-isbn',
     author: 'Faux Hawk',
     branch: 'downtown'
-  },
-  {
-    title: '[Redacted]',
-    isbn: 'a-third-fake-isbn',
-    author: 'Closed Telemetry',
-    branch: 'riverside'
-  },
-  {
-    title: 'Be a hero: fixing the things you broke',
-    isbn: 'a-fourth-fake-isbn',
-    author: '10x Developer',
-    branch: 'downtown'
-  }
-]
-
-const magazines = [
-  {
-    title: 'Reli Updates Weekly',
-    issue: 1,
-    branch: 'riverside'
-  },
-  {
-    title: 'Reli Updates Weekly',
-    issue: 2,
-    branch: 'downtown'
   }
 ]
 
@@ -59,7 +34,7 @@ function getTypeDefs(gql) {
     type Library {
       branch: String!
       books: [Book!]
-      magazines: [Magazine]
+      boom: String
     }
 
     type Book {
@@ -72,18 +47,13 @@ function getTypeDefs(gql) {
       name: String!
     }
 
-    type Magazine {
-      title: String!
-      issue: Int
-    }
-
     type Query {
       books: [Book]
       hello: String
       boom: String
       paramQuery(blah: String!, blee: String): String!
       libraries: [Library]
-      library(branch: String!): Library
+      library(branch: String!): [Library]
     }
 
     type Mutation {
@@ -110,7 +80,7 @@ const resolvers = {
     library: (_, {branch}) => {
       const promise = new Promise((resolve) => {
         setTimeout(() => {
-          const filtered = libraries.find(library => library.branch === branch)
+          const filtered = libraries.filter(library => library.branch === branch)
           resolve(filtered)
         }, 0)
       })
@@ -132,9 +102,6 @@ const resolvers = {
   Library: {
     books(parent) {
       return books.filter(book => book.branch === parent.branch)
-    },
-    magazines(parent) {
-      return magazines.filter(magazine => magazine.branch === parent.branch)
     }
   },
   Book: {
diff --git a/tests/integration/bootstrap.test.js b/tests/integration/bootstrap.test.js
index 67478b3..2942c51 100644
--- a/tests/integration/bootstrap.test.js
+++ b/tests/integration/bootstrap.test.js
@@ -11,22 +11,20 @@ const { setupEnvConfig } = require('../agent-testing')
 
 const INDEX_PATH = '../..'
 
-tap.test('Should export createPlugin when loaded', (t) => {
-  const createPlugin = require(INDEX_PATH)
-  t.ok(createPlugin)
+tap.test('Should export plugin when loaded', (t) => {
+  const plugin = require(INDEX_PATH)
+  t.ok(plugin)
 
   resetModuleCache(() => {
     t.end()
   })
 })
 
-tap.test('should create noop plugin when agent disabled', (t) => {
+tap.test('should export noop plugin when agent disabled', (t) => {
   // w/o a config file the agent will be disabled by default
-  const createPlugin = require('../..')
-  t.ok(createPlugin)
-
-  const plugin = createPlugin()
+  const plugin = require('../..')
   t.ok(plugin)
+
   t.notOk(plugin.requestDidStart)
 
   resetModuleCache(() => {
@@ -34,13 +32,10 @@ tap.test('should create noop plugin when agent disabled', (t) => {
   })
 })
 
-tap.test('should create full plugin when agent enabled', (t) => {
+tap.test('should export full plugin when agent enabled', (t) => {
   setupEnvConfig(t)
 
-  const createPlugin = require('../..')
-  t.ok(createPlugin)
-
-  const plugin = createPlugin()
+  const plugin = require('../..')
   t.ok(plugin)
   t.ok(plugin.requestDidStart)
 
diff --git a/tests/integration/config-capture-scalars.test.js b/tests/integration/config-capture-scalars.test.js
deleted file mode 100644
index 855cc93..0000000
--- a/tests/integration/config-capture-scalars.test.js
+++ /dev/null
@@ -1,193 +0,0 @@
-/*
- * Copyright 2020 New Relic Corporation. All rights reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-'use strict'
-
-const { executeQuery } = require('../test-client')
-const { setupEnvConfig } = require('../agent-testing')
-
-const OPERATION_PREFIX = 'GraphQL/operation/ApolloServer'
-const RESOLVE_PREFIX = 'GraphQL/resolve/ApolloServer'
-const TRANSACTION_PREFIX = 'WebTransaction/Expressjs/POST'
-
-const { setupApolloServerTests } = require('../apollo-server-setup')
-
-setupApolloServerTests({
-  suiteName: 'default',
-  createTests: createNoScalarTests
-})
-
-setupApolloServerTests({
-  suiteName: 'captureScalars: false',
-  createTests: createNoScalarTests,
-  pluginConfig: {
-    captureScalars: false
-  }
-})
-
-setupApolloServerTests({
-  suiteName: 'captureScalars: true',
-  createTests: createScalarTests,
-  pluginConfig: {
-    captureScalars: true
-  }
-})
-
-function createNoScalarTests(t) {
-  setupEnvConfig(t)
-
-  t.test('multi-level, should not capture scalar fields', (t) => {
-    const { helper, serverUrl } = t.context
-
-    const expectedName = 'GetAllForLibrary'
-    const query = `query ${expectedName} {
-      library(branch: "downtown") {
-        books {
-          title
-          author {
-            name
-          }
-        }
-        magazines {
-          title
-          issue
-        }
-      }
-    }`
-
-    const deepestPath = 'library.books.author.name'
-
-    helper.agent.on('transactionFinished', (transaction) => {
-      const operationPart = `query/${expectedName}/${deepestPath}`
-      const expectedSegments = [{
-        name: `${TRANSACTION_PREFIX}//${operationPart}`,
-        children: [
-          { name: 'Nodejs/Middleware/Expressjs/query' },
-          { name: 'Nodejs/Middleware/Expressjs/expressInit' },
-          {
-            name: 'Expressjs/Router: /',
-            children: [
-              { name: 'Nodejs/Middleware/Expressjs/<anonymous>' },
-              { name: 'Nodejs/Middleware/Expressjs/corsMiddleware' },
-              { name: 'Nodejs/Middleware/Expressjs/jsonParser' },
-              { name: 'Nodejs/Middleware/Expressjs/<anonymous>' },
-              {
-                name: 'Nodejs/Middleware/Expressjs/<anonymous>',
-                children: [{
-                  name: `${OPERATION_PREFIX}/${operationPart}`,
-                  children: [
-                    {
-                      name: `${RESOLVE_PREFIX}/library`,
-                      children: [{
-                        name: 'timers.setTimeout',
-                        children: [{
-                          name: 'Callback: <anonymous>'
-                        }]
-                      }]
-                    },
-                    { name: `${RESOLVE_PREFIX}/library.books` },
-                    { name: `${RESOLVE_PREFIX}/library.books.author` },
-                    { name: `${RESOLVE_PREFIX}/library.books.author` },
-                    { name: `${RESOLVE_PREFIX}/library.magazines`}
-                  ]
-                }
-              ]
-            }
-          ]
-        }]
-      }]
-
-      // Exact match to ensure no extra fields snuck in
-      t.exactSegments(transaction.trace.root, expectedSegments)
-    })
-
-    executeQuery(serverUrl, query, (err) => {
-      t.error(err)
-      t.end()
-    })
-  })
-}
-
-function createScalarTests(t) {
-  setupEnvConfig(t)
-
-  t.test('multi-level, should capture scalar fields', (t) => {
-    const { helper, serverUrl } = t.context
-
-    const expectedName = 'GetAllForLibrary'
-    const query = `query ${expectedName} {
-      library(branch: "downtown") {
-        books {
-          title
-          author {
-            name
-          }
-        }
-        magazines {
-          title
-          issue
-        }
-      }
-    }`
-
-    const deepestPath = 'library.books.author.name'
-
-    helper.agent.on('transactionFinished', (transaction) => {
-      const operationPart = `query/${expectedName}/${deepestPath}`
-      const expectedSegments = [{
-        name: `${TRANSACTION_PREFIX}//${operationPart}`,
-        children: [
-          { name: 'Nodejs/Middleware/Expressjs/query' },
-          { name: 'Nodejs/Middleware/Expressjs/expressInit' },
-          {
-            name: 'Expressjs/Router: /',
-            children: [
-              { name: 'Nodejs/Middleware/Expressjs/<anonymous>' },
-              { name: 'Nodejs/Middleware/Expressjs/corsMiddleware' },
-              { name: 'Nodejs/Middleware/Expressjs/jsonParser' },
-              { name: 'Nodejs/Middleware/Expressjs/<anonymous>' },
-              {
-                name: 'Nodejs/Middleware/Expressjs/<anonymous>',
-                children: [{
-                  name: `${OPERATION_PREFIX}/${operationPart}`,
-                  children: [
-                    {
-                      name: `${RESOLVE_PREFIX}/library`,
-                      children: [{
-                        name: 'timers.setTimeout',
-                        children: [{
-                          name: 'Callback: <anonymous>'
-                        }]
-                      }]
-                    },
-                    { name: `${RESOLVE_PREFIX}/library.books` },
-                    { name: `${RESOLVE_PREFIX}/library.books.title` },
-                    { name: `${RESOLVE_PREFIX}/library.books.author` },
-                    { name: `${RESOLVE_PREFIX}/library.books.author.name` },
-                    { name: `${RESOLVE_PREFIX}/library.books.title` },
-                    { name: `${RESOLVE_PREFIX}/library.books.author` },
-                    { name: `${RESOLVE_PREFIX}/library.books.author.name` },
-                    { name: `${RESOLVE_PREFIX}/library.magazines`},
-                    { name: `${RESOLVE_PREFIX}/library.magazines.title`},
-                    { name: `${RESOLVE_PREFIX}/library.magazines.issue`}
-                  ]
-                }
-              ]
-            }
-          ]
-        }]
-      }]
-
-      // Exact match to ensure no extra fields snuck in
-      t.exactSegments(transaction.trace.root, expectedSegments)
-    })
-
-    executeQuery(serverUrl, query, (err) => {
-      t.error(err)
-      t.end()
-    })
-  })
-}
-
diff --git a/tests/integration/metrics.test.js b/tests/integration/metrics.test.js
index 409b7c1..99f282a 100644
--- a/tests/integration/metrics.test.js
+++ b/tests/integration/metrics.test.js
@@ -91,7 +91,8 @@ function createMetricsTests(t) {
         `${OPERATION_PREFIX}/${operationPart}`,
         `${RESOLVE_PREFIX}/libraries`,
         `${RESOLVE_PREFIX}/books`,
-        `${RESOLVE_PREFIX}/author`
+        `${RESOLVE_PREFIX}/author`,
+        `${RESOLVE_PREFIX}/name`
       ])
     })
 
@@ -132,7 +133,9 @@ function createMetricsTests(t) {
         `${OPERATION_PREFIX}/${operationPart1}`,
         `${RESOLVE_PREFIX}/library`,
         `${RESOLVE_PREFIX}/books`,
-        `${RESOLVE_PREFIX}/author`
+        `${RESOLVE_PREFIX}/title`,
+        `${RESOLVE_PREFIX}/author`,
+        `${RESOLVE_PREFIX}/name`
       ]
 
       const operationMetrics2 = [
diff --git a/tests/versioned/apollo-server-express/apollo-server-express-setup.js b/tests/versioned/apollo-server-express/apollo-server-express-setup.js
index 85ad153..4291c71 100644
--- a/tests/versioned/apollo-server-express/apollo-server-express-setup.js
+++ b/tests/versioned/apollo-server-express/apollo-server-express-setup.js
@@ -14,7 +14,7 @@ const { getTypeDefs, resolvers } = require('../../data-definitions')
 
 const WEB_FRAMEWORK = 'Expressjs'
 
-function setupApolloServerExpressTests({suiteName, createTests, pluginConfig}, config) {
+function setupApolloServerExpressTests({suiteName, createTests}, config) {
   tap.test(`apollo-server-express: ${suiteName}`, (t) => {
     t.autoend()
 
@@ -30,7 +30,7 @@ function setupApolloServerExpressTests({suiteName, createTests, pluginConfig}, c
       const nrApi = helper.getAgentApi()
 
       // TODO: eventually use proper function for instrumenting and not .shim
-      const plugin = createPlugin(nrApi.shim, pluginConfig)
+      const plugin = createPlugin(nrApi.shim)
 
       const express = require('express')
 
diff --git a/tests/versioned/apollo-server-express/segments.test.js b/tests/versioned/apollo-server-express/segments.test.js
index a6f6847..8bc7a3f 100644
--- a/tests/versioned/apollo-server-express/segments.test.js
+++ b/tests/versioned/apollo-server-express/segments.test.js
@@ -6,8 +6,6 @@
 'use strict'
 
 const { setupApolloServerExpressTests } = require('./apollo-server-express-setup')
-const segmentsDefaultTests = require('../express-segments-default-tests')
-const segmentsScalarTests = require('../express-segments-scalar-tests')
+const segmentsTests = require('../express-segments-tests')
 
-setupApolloServerExpressTests(segmentsDefaultTests)
-setupApolloServerExpressTests(segmentsScalarTests)
+setupApolloServerExpressTests(segmentsTests)
diff --git a/tests/versioned/apollo-server-fastify/apollo-server-fastify-setup.js b/tests/versioned/apollo-server-fastify/apollo-server-fastify-setup.js
index 5565df0..7a0b66b 100644
--- a/tests/versioned/apollo-server-fastify/apollo-server-fastify-setup.js
+++ b/tests/versioned/apollo-server-fastify/apollo-server-fastify-setup.js
@@ -14,7 +14,7 @@ const { getTypeDefs, resolvers } = require('../../data-definitions')
 
 const WEB_FRAMEWORK = 'Nodejs'
 
-function setupApolloServerFastifyTests({suiteName, createTests, pluginConfig}, config) {
+function setupApolloServerFastifyTests({suiteName, createTests}, config) {
   tap.test(`apollo-server-fastify: ${suiteName}`, (t) => {
     t.autoend()
 
@@ -32,7 +32,7 @@ function setupApolloServerFastifyTests({suiteName, createTests, pluginConfig}, c
       app = require('fastify')()
 
       // TODO: eventually use proper function for instrumenting and not .shim
-      const plugin = createPlugin(nrApi.shim, pluginConfig)
+      const plugin = createPlugin(nrApi.shim)
 
       // Do after instrumentation to ensure hapi isn't loaded too soon.
       const { ApolloServer, gql } = require('apollo-server-fastify')
diff --git a/tests/versioned/apollo-server-fastify/segments.test.js b/tests/versioned/apollo-server-fastify/segments.test.js
index 3cc4877..da1d9f5 100644
--- a/tests/versioned/apollo-server-fastify/segments.test.js
+++ b/tests/versioned/apollo-server-fastify/segments.test.js
@@ -17,10 +17,7 @@ const { setupApolloServerFastifyTests } = require('./apollo-server-fastify-setup
 
 setupApolloServerFastifyTests({
   suiteName: 'fastify segments',
-  createTests: createFastifySegmentsTests,
-  pluginConfig: {
-    captureScalars: true
-  }
+  createTests: createFastifySegmentsTests
 })
 
 function createFastifySegmentsTests(t, frameworkName) {
diff --git a/tests/versioned/apollo-server-hapi/apollo-server-hapi-setup.js b/tests/versioned/apollo-server-hapi/apollo-server-hapi-setup.js
index 5c0ace6..e67815a 100644
--- a/tests/versioned/apollo-server-hapi/apollo-server-hapi-setup.js
+++ b/tests/versioned/apollo-server-hapi/apollo-server-hapi-setup.js
@@ -14,7 +14,7 @@ const { getTypeDefs, resolvers } = require('../../data-definitions')
 
 const WEB_FRAMEWORK = 'Hapi'
 
-function setupApolloServerHapiTests({suiteName, createTests, pluginConfig}, config) {
+function setupApolloServerHapiTests({suiteName, createTests}, config) {
   tap.test(`apollo-server-hapi: ${suiteName}`, (t) => {
     t.autoend()
 
@@ -30,7 +30,7 @@ function setupApolloServerHapiTests({suiteName, createTests, pluginConfig}, conf
       const nrApi = helper.getAgentApi()
 
       // TODO: eventually use proper function for instrumenting and not .shim
-      const plugin = createPlugin(nrApi.shim, pluginConfig)
+      const plugin = createPlugin(nrApi.shim)
 
       const Hapi = require('@hapi/hapi')
 
diff --git a/tests/versioned/apollo-server-hapi/segments.test.js b/tests/versioned/apollo-server-hapi/segments.test.js
index e6064b3..b048de3 100644
--- a/tests/versioned/apollo-server-hapi/segments.test.js
+++ b/tests/versioned/apollo-server-hapi/segments.test.js
@@ -17,10 +17,7 @@ const { setupApolloServerHapiTests } = require('./apollo-server-hapi-setup')
 
 setupApolloServerHapiTests({
   suiteName: 'hapi segments',
-  createTests: createHapiSegmentsTests,
-  pluginConfig: {
-    captureScalars: true
-  }
+  createTests: createHapiSegmentsTests
 })
 
 function createHapiSegmentsTests(t, frameworkName) {
diff --git a/tests/versioned/apollo-server-koa/apollo-server-koa-setup.js b/tests/versioned/apollo-server-koa/apollo-server-koa-setup.js
index 2e5b9c8..3e98440 100644
--- a/tests/versioned/apollo-server-koa/apollo-server-koa-setup.js
+++ b/tests/versioned/apollo-server-koa/apollo-server-koa-setup.js
@@ -14,7 +14,7 @@ const { getTypeDefs, resolvers } = require('../../data-definitions')
 
 const WEB_FRAMEWORK = 'WebFrameworkUri/Koa'
 
-function setupApolloServerKoaTests({suiteName, createTests, pluginConfig}, config) {
+function setupApolloServerKoaTests({suiteName, createTests}, config) {
   tap.test(`apollo-server-koa: ${suiteName}`, (t) => {
     t.autoend()
 
@@ -35,7 +35,7 @@ function setupApolloServerKoaTests({suiteName, createTests, pluginConfig}, confi
       app = new Koa()
 
       // TODO: eventually use proper function for instrumenting and not .shim
-      const plugin = createPlugin(nrApi.shim, pluginConfig)
+      const plugin = createPlugin(nrApi.shim)
 
       const graphqlPath = '/gql'
 
diff --git a/tests/versioned/apollo-server-koa/segments.test.js b/tests/versioned/apollo-server-koa/segments.test.js
index 04a2524..2c5b0ff 100644
--- a/tests/versioned/apollo-server-koa/segments.test.js
+++ b/tests/versioned/apollo-server-koa/segments.test.js
@@ -17,10 +17,7 @@ const { setupApolloServerKoaTests } = require('./apollo-server-koa-setup')
 
 setupApolloServerKoaTests({
   suiteName: 'koa segments',
-  createTests: createKoaSegmentsTests,
-  pluginConfig: {
-    captureScalars: true
-  }
+  createTests: createKoaSegmentsTests
 })
 
 function createKoaSegmentsTests(t, frameworkName) {
diff --git a/tests/versioned/apollo-server/segments.test.js b/tests/versioned/apollo-server/segments.test.js
index 9cca53f..491ed93 100644
--- a/tests/versioned/apollo-server/segments.test.js
+++ b/tests/versioned/apollo-server/segments.test.js
@@ -6,8 +6,6 @@
 'use strict'
 
 const { setupApolloServerTests } = require('../../apollo-server-setup')
-const expressSegmentsDefaultTests = require('../express-segments-default-tests')
-const expressSegmentsScalarTests = require('../express-segments-scalar-tests')
+const expressSegmentsTests = require('../express-segments-tests')
 
-setupApolloServerTests(expressSegmentsDefaultTests)
-setupApolloServerTests(expressSegmentsScalarTests)
+setupApolloServerTests(expressSegmentsTests)
diff --git a/tests/versioned/express-segments-default-tests.js b/tests/versioned/express-segments-default-tests.js
deleted file mode 100644
index 82ccfb2..0000000
--- a/tests/versioned/express-segments-default-tests.js
+++ /dev/null
@@ -1,653 +0,0 @@
-/*
- * Copyright 2020 New Relic Corporation. All rights reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
-
-'use strict'
-
-const { executeQuery, executeQueryBatch } = require('../test-client')
-
-const ANON_PLACEHOLDER = '<anonymous>'
-const UNKNOWN_OPERATION = '<unknown>'
-
-const OPERATION_PREFIX = 'GraphQL/operation/ApolloServer'
-const RESOLVE_PREFIX = 'GraphQL/resolve/ApolloServer'
-
-/**
- * Creates a set of standard segment naming and nesting tests to run
- * against express-based apollo-server libraries.
- * It is required that t.context.helper and t.context.serverUrl are set.
- * @param {*} t a tap test instance
- */
-function createSegmentsTests(t, frameworkName) {
-  const TRANSACTION_PREFIX = `WebTransaction/${frameworkName}/POST`
-
-  t.test('anonymous query, single level', (t) => {
-    const { helper, serverUrl } = t.context
-
-    const query = `query {
-      hello
-    }`
-
-    helper.agent.on('transactionFinished', (transaction) => {
-      const operationPart = `query/${ANON_PLACEHOLDER}/hello`
-      const expectedSegments = [{
-        name: `${TRANSACTION_PREFIX}//${operationPart}`,
-        children: [{
-          name: 'Expressjs/Router: /',
-          children: [{
-            name: 'Nodejs/Middleware/Expressjs/<anonymous>',
-            children: [{
-              name: `${OPERATION_PREFIX}/${operationPart}`,
-              children: [{
-                name: `${RESOLVE_PREFIX}/hello`
-              }]
-            }]
-          }]
-        }]
-      }]
-
-      t.segments(transaction.trace.root, expectedSegments)
-    })
-
-    executeQuery(serverUrl, query, (err, result) => {
-      t.error(err)
-      checkResult(t, result, () => {
-        t.end()
-      })
-    })
-  })
-
-  t.test('named query, single level', (t) => {
-    const { helper, serverUrl } = t.context
-
-    const expectedName = 'HeyThere'
-    const query = `query ${expectedName} {
-      hello
-    }`
-
-    helper.agent.on('transactionFinished', (transaction) => {
-      const operationPart = `query/${expectedName}/hello`
-      const expectedSegments = [{
-        name: `${TRANSACTION_PREFIX}//${operationPart}`,
-        children: [{
-          name: 'Expressjs/Router: /',
-          children: [{
-            name: 'Nodejs/Middleware/Expressjs/<anonymous>',
-            children: [{
-              name: `${OPERATION_PREFIX}/${operationPart}`,
-              children: [{
-                name: `${RESOLVE_PREFIX}/hello`
-              }]
-            }]
-          }]
-        }]
-      }]
-
-      t.segments(transaction.trace.root, expectedSegments)
-    })
-
-    executeQuery(serverUrl, query, (err, result) => {
-      t.error(err)
-      checkResult(t, result, () => {
-        t.end()
-      })
-    })
-  })
-
-  t.test('anonymous query, multi-level', (t) => {
-    const { helper, serverUrl } = t.context
-
-    const query = `query {
-      libraries {
-        books {
-          title
-          author {
-            name
-          }
-        }
-      }
-    }`
-
-    const deepestPath = 'libraries.books.author.name'
-
-    helper.agent.on('transactionFinished', (transaction) => {
-      const operationPart = `query/${ANON_PLACEHOLDER}/${deepestPath}`
-      const expectedSegments = [{
-        name: `${TRANSACTION_PREFIX}//${operationPart}`,
-        children: [{
-          name: 'Expressjs/Router: /',
-          children: [{
-            name: 'Nodejs/Middleware/Expressjs/<anonymous>',
-            children: [{
-              name: `${OPERATION_PREFIX}/${operationPart}`,
-              children: [
-                { name: `${RESOLVE_PREFIX}/libraries` },
-                { name: `${RESOLVE_PREFIX}/libraries.books` },
-                { name: `${RESOLVE_PREFIX}/libraries.books.author` }
-              ]
-            }]
-          }]
-        }]
-      }]
-
-      t.segments(transaction.trace.root, expectedSegments)
-    })
-
-    executeQuery(serverUrl, query, (err, result) => {
-      t.error(err)
-      checkResult(t, result, () => {
-        t.end()
-      })
-    })
-  })
-
-  t.test('named query, multi-level should return deepest path', (t) => {
-    const { helper, serverUrl } = t.context
-
-    const expectedName = 'GetBooksByLibrary'
-    const query = `query ${expectedName} {
-      libraries {
-        books {
-          title
-          author {
-            name
-          }
-        }
-      }
-    }`
-
-    const deepestPath = 'libraries.books.author.name'
-
-    helper.agent.on('transactionFinished', (transaction) => {
-      const operationPart = `query/${expectedName}/${deepestPath}`
-      const expectedSegments = [{
-        name: `${TRANSACTION_PREFIX}//${operationPart}`,
-        children: [{
-          name: 'Expressjs/Router: /',
-          children: [{
-            name: 'Nodejs/Middleware/Expressjs/<anonymous>',
-            children: [{
-              name: `${OPERATION_PREFIX}/${operationPart}`,
-              children: [
-                { name: `${RESOLVE_PREFIX}/libraries` },
-                { name: `${RESOLVE_PREFIX}/libraries.books` },
-                { name: `${RESOLVE_PREFIX}/libraries.books.author` }
-              ]
-            }]
-          }]
-        }]
-      }]
-
-      t.segments(transaction.trace.root, expectedSegments)
-    })
-
-    executeQuery(serverUrl, query, (err, result) => {
-      t.error(err)
-      checkResult(t, result, () => {
-        t.end()
-      })
-    })
-  })
-
-  t.test('named query, multi-level, should choose *first* deepest-path', (t) => {
-    const { helper, serverUrl } = t.context
-
-    const expectedName = 'GetBooksByLibrary'
-    const query = `query ${expectedName} {
-      libraries {
-        books {
-          title
-          isbn
-        }
-      }
-    }`
-
-    // .isbn is the same length but title will be first so that path should be used
-    const firstDeepestPath = 'libraries.books.title'
-
-    helper.agent.on('transactionFinished', (transaction) => {
-      const operationPart = `query/${expectedName}/${firstDeepestPath}`
-      const expectedSegments = [{
-        name: `${TRANSACTION_PREFIX}//${operationPart}`,
-        children: [{
-          name: 'Expressjs/Router: /',
-          children: [{
-            name: 'Nodejs/Middleware/Expressjs/<anonymous>',
-            children: [{
-              name: `${OPERATION_PREFIX}/${operationPart}`,
-              children: [
-                { name: `${RESOLVE_PREFIX}/libraries` },
-                { name: `${RESOLVE_PREFIX}/libraries.books` }
-              ]
-            }]
-          }]
-        }]
-      }]
-
-      t.segments(transaction.trace.root, expectedSegments)
-    })
-
-    executeQuery(serverUrl, query, (err, result) => {
-      t.error(err)
-      checkResult(t, result, () => {
-        t.end()
-      })
-    })
-  })
-
-  t.test('anonymous mutation, single level', (t) => {
-    const { helper, serverUrl } = t.context
-
-    const query = `mutation {
-      addThing(name: "added thing!")
-    }`
-
-    helper.agent.on('transactionFinished', (transaction) => {
-      const operationPart = `mutation/${ANON_PLACEHOLDER}/addThing`
-      const expectedSegments = [{
-        name: `${TRANSACTION_PREFIX}//${operationPart}`,
-        children: [{
-          name: 'Expressjs/Router: /',
-          children: [{
-            name: 'Nodejs/Middleware/Expressjs/<anonymous>',
-            children: [{
-              name: `${OPERATION_PREFIX}/${operationPart}`,
-              children: [{
-                name: `${RESOLVE_PREFIX}/addThing`,
-                children: [{
-                  name: 'timers.setTimeout',
-                  children: [{
-                    name: 'Callback: namedCallback'
-                  }]
-                }]
-              }]
-            }]
-          }]
-        }]
-      }]
-
-      t.segments(transaction.trace.root, expectedSegments)
-    })
-
-    executeQuery(serverUrl, query, (err, result) => {
-      t.error(err)
-      checkResult(t, result, () => {
-        t.end()
-      })
-    })
-  })
-
-  t.test('named mutation, single level, should use mutation name', (t) => {
-    const { helper, serverUrl } = t.context
-
-    const expectedName = 'AddThing'
-    const query = `mutation ${expectedName} {
-      addThing(name: "added thing!")
-    }`
-
-    helper.agent.on('transactionFinished', (transaction) => {
-      const operationPart = `mutation/${expectedName}/addThing`
-      const expectedSegments = [{
-        name: `${TRANSACTION_PREFIX}//${operationPart}`,
-        children: [{
-          name: 'Expressjs/Router: /',
-          children: [{
-            name: 'Nodejs/Middleware/Expressjs/<anonymous>',
-            children: [{
-              name: `${OPERATION_PREFIX}/${operationPart}`,
-              children: [{
-                name: `${RESOLVE_PREFIX}/addThing`,
-                children: [{
-                  name: 'timers.setTimeout',
-                  children: [{
-                    name: 'Callback: namedCallback'
-                  }]
-                }]
-              }]
-            }]
-          }]
-        }]
-      }]
-
-      t.segments(transaction.trace.root, expectedSegments)
-    })
-
-    executeQuery(serverUrl, query, (err, result) => {
-      t.error(err)
-      checkResult(t, result, () => {
-        t.end()
-      })
-    })
-  })
-
-  t.test('anonymous query, with params', (t) => {
-    const { helper, serverUrl } = t.context
-
-    const query = `query {
-      paramQuery(blah: "blah", blee: "blee")
-    }`
-
-    helper.agent.on('transactionFinished', (transaction) => {
-      const operationPart = `query/${ANON_PLACEHOLDER}/paramQuery`
-      const expectedSegments = [{
-        name: `${TRANSACTION_PREFIX}//${operationPart}`,
-        children: [{
-          name: 'Expressjs/Router: /',
-          children: [{
-            name: 'Nodejs/Middleware/Expressjs/<anonymous>',
-            children: [{
-              name: `${OPERATION_PREFIX}/${operationPart}`,
-              children: [
-                { name: `${RESOLVE_PREFIX}/paramQuery` }
-              ]
-            }]
-          }]
-        }]
-      }]
-
-      t.segments(transaction.trace.root, expectedSegments)
-    })
-
-    executeQuery(serverUrl, query, (err, result) => {
-      t.error(err)
-      checkResult(t, result, () => {
-        t.end()
-      })
-    })
-  })
-
-  t.test('named query, with params', (t) => {
-    const { helper, serverUrl } = t.context
-
-    const expectedName = 'BlahQuery'
-    const query = `query ${expectedName} {
-      paramQuery(blah: "blah")
-    }`
-
-    helper.agent.on('transactionFinished', (transaction) => {
-      const operationPart = `query/${expectedName}/paramQuery`
-      const expectedSegments = [{
-        name: `${TRANSACTION_PREFIX}//${operationPart}`,
-        children: [{
-          name: 'Expressjs/Router: /',
-          children: [{
-            name: 'Nodejs/Middleware/Expressjs/<anonymous>',
-            children: [{
-              name: `${OPERATION_PREFIX}/${operationPart}`,
-              children: [
-                { name: `${RESOLVE_PREFIX}/paramQuery` }
-              ]
-            }]
-          }]
-        }]
-      }]
-
-      t.segments(transaction.trace.root, expectedSegments)
-    })
-
-    executeQuery(serverUrl, query, (err, result) => {
-      t.error(err)
-      checkResult(t, result, () => {
-        t.end()
-      })
-    })
-  })
-
-  t.test('named query, with params, multi-level', (t) => {
-    const { helper, serverUrl } = t.context
-
-    const expectedName = 'GetBookForLibrary'
-    const query = `query ${expectedName} {
-      library(branch: "downtown") {
-        books {
-          title
-          author {
-            name
-          }
-        }
-      }
-    }`
-
-    const deepestPath = 'library.books.author.name'
-
-    helper.agent.on('transactionFinished', (transaction) => {
-      const operationPart = `query/${expectedName}/${deepestPath}`
-      const expectedSegments = [{
-        name: `${TRANSACTION_PREFIX}//${operationPart}`,
-        children: [{
-          name: 'Expressjs/Router: /',
-          children: [{
-            name: 'Nodejs/Middleware/Expressjs/<anonymous>',
-            children: [{
-              name: `${OPERATION_PREFIX}/${operationPart}`,
-              children: [
-                {
-                  name: `${RESOLVE_PREFIX}/library`,
-                  children: [{
-                    name: 'timers.setTimeout',
-                    children: [{
-                      name: 'Callback: <anonymous>'
-                    }]
-                  }]
-                },
-                { name: `${RESOLVE_PREFIX}/library.books` },
-                { name: `${RESOLVE_PREFIX}/library.books.author` }
-              ]
-            }]
-          }]
-        }]
-      }]
-
-      t.segments(transaction.trace.root, expectedSegments)
-    })
-
-    executeQuery(serverUrl, query, (err, result) => {
-      t.error(err)
-      checkResult(t, result, () => {
-        t.end()
-      })
-    })
-  })
-
-  t.test('batch query should include segments for nested queries', (t) => {
-    const { helper, serverUrl } = t.context
-
-    const expectedName1 = 'GetBookForLibrary'
-    const query1 = `query ${expectedName1} {
-      library(branch: "downtown") {
-        books {
-          title
-          author {
-            name
-          }
-        }
-      }
-    }`
-
-    const query2 = `mutation {
-      addThing(name: "added thing!")
-    }`
-
-    const deepestPath1 = 'library.books.author.name'
-
-    const queries = [query1, query2]
-
-    helper.agent.on('transactionFinished', (transaction) => {
-      const operationPart1 = `query/${expectedName1}/${deepestPath1}`
-      const expectedQuery1Name = `${operationPart1}`
-      const operationPart2 = `mutation/${ANON_PLACEHOLDER}/addThing`
-      const expectedQuery2Name = `${operationPart2}`
-
-      const batchTransactionPrefix = `${TRANSACTION_PREFIX}//batch`
-
-      const expectedSegments = [{
-        name: `${batchTransactionPrefix}/${expectedQuery1Name}/${expectedQuery2Name}`,
-        children: [{
-          name: 'Expressjs/Router: /',
-          children: [{
-            name: 'Nodejs/Middleware/Expressjs/<anonymous>',
-            children: [
-              {
-                name: `${OPERATION_PREFIX}/${operationPart1}`,
-                children: [
-                  {
-                    name: `${RESOLVE_PREFIX}/library`,
-                    children: [{
-                      name: 'timers.setTimeout',
-                      children: [{
-                        name: 'Callback: <anonymous>'
-                      }]
-                    }]
-                  },
-                  { name: `${RESOLVE_PREFIX}/library.books` },
-                  { name: `${RESOLVE_PREFIX}/library.books.author` }
-                ]
-              },
-              {
-                name: `${OPERATION_PREFIX}/${operationPart2}`,
-                children: [{
-                  name: `${RESOLVE_PREFIX}/addThing`,
-                  children: [{
-                    name: 'timers.setTimeout',
-                    children: [{
-                      name: 'Callback: namedCallback'
-                    }]
-                  }]
-                }]
-              }
-            ]
-          }]
-        }]
-      }]
-
-      t.segments(transaction.trace.root, expectedSegments)
-    })
-
-    executeQueryBatch(serverUrl, queries, (err, result) => {
-      t.error(err)
-      checkResult(t, result, () => {
-        t.equal(result.length, 2)
-
-        t.end()
-      })
-    })
-  })
-
-  // there will be no document/AST nor resolved operation
-  t.test('when the query cannot be parsed, should have operation placeholder', (t) => {
-    const { helper, serverUrl } = t.context
-
-    const invalidQuery = `query {
-      libraries {
-        books {
-          title
-          author {
-            name
-          }
-        }
-      }
-    ` // missing closing }
-
-    helper.agent.on('transactionFinished', (transaction) => {
-      const expectedSegments = [{
-        name: `${TRANSACTION_PREFIX}//*`,
-        children: [{
-          name: 'Expressjs/Router: /',
-          children: [{
-            name: 'Nodejs/Middleware/Expressjs/<anonymous>',
-            children: [{
-              name: `${OPERATION_PREFIX}/${UNKNOWN_OPERATION}`
-            }]
-          }]
-        }]
-      }]
-
-      t.segments(transaction.trace.root, expectedSegments)
-    })
-
-    executeQuery(serverUrl, invalidQuery, (err, result) => {
-      t.error(err)
-
-      t.ok(result)
-      t.ok(result.errors)
-      t.equal(result.errors.length, 1) // should have one parsing error
-
-      const [parseError] = result.errors
-      t.equal(parseError.extensions.code, 'GRAPHQL_PARSE_FAILED')
-
-      t.end()
-    })
-  })
-
-  // if parse succeeds but validation fails, there will not be a resolved operation
-  // but the document/AST can still be leveraged for what was intended.
-  t.test('when cannot validate, should include operation segment', (t) => {
-    const { helper, serverUrl } = t.context
-
-    const invalidQuery = `query {
-      libraries {
-        books {
-          title
-          doesnotexist {
-            name
-          }
-        }
-      }
-    }`
-
-    const deepestPath = 'libraries.books.doesnotexist.name'
-
-    helper.agent.on('transactionFinished', (transaction) => {
-      const operationPart = `query/${ANON_PLACEHOLDER}/${deepestPath}`
-      const expectedSegments = [{
-        name: `${TRANSACTION_PREFIX}//${operationPart}`,
-        children: [{
-          name: 'Expressjs/Router: /',
-          children: [{
-            name: 'Nodejs/Middleware/Expressjs/<anonymous>',
-            children: [{
-              name: `${OPERATION_PREFIX}/${operationPart}`
-            }]
-          }]
-        }]
-      }]
-
-      t.segments(transaction.trace.root, expectedSegments)
-    })
-
-    executeQuery(serverUrl, invalidQuery, (err, result) => {
-      t.error(err)
-
-      t.ok(result)
-      t.ok(result.errors)
-      t.equal(result.errors.length, 1) // should have one parsing error
-
-      const [parseError] = result.errors
-      t.equal(parseError.extensions.code, 'GRAPHQL_VALIDATION_FAILED')
-
-      t.end()
-    })
-  })
-}
-
-/**
- * Verify we didn't break anything outright and
- * test is setup correctly for functioning calls.
- */
-function checkResult(t, result, callback) {
-  t.ok(result)
-
-  if (result.errors) {
-    result.errors.forEach((error) => {
-      t.error(error)
-    })
-  }
-
-  setImmediate(callback)
-}
-
-module.exports = {
-  suiteName: 'express segments default configuration',
-  createTests: createSegmentsTests
-}
diff --git a/tests/versioned/express-segments-scalar-tests.js b/tests/versioned/express-segments-tests.js
similarity index 99%
rename from tests/versioned/express-segments-scalar-tests.js
rename to tests/versioned/express-segments-tests.js
index fe94157..fa9f64b 100644
--- a/tests/versioned/express-segments-scalar-tests.js
+++ b/tests/versioned/express-segments-tests.js
@@ -657,7 +657,6 @@ function checkResult(t, result, callback) {
 }
 
 module.exports = {
-  suiteName: 'express segments with scalars',
-  createTests: createSegmentsTests,
-  pluginConfig: { captureScalars: true }
+  testSuite: 'express segments',
+  createTests: createSegmentsTests
 }