diff --git a/services/w3c/w3c-validation-helper.js b/services/w3c/w3c-validation-helper.js
new file mode 100644
index 0000000000000000000000000000000000000000..3f0a75f4016db4f23f8aefa335cb01b790d33ed0
--- /dev/null
+++ b/services/w3c/w3c-validation-helper.js
@@ -0,0 +1,156 @@
+'use strict'
+
+const html5Expression =
+  '^HTML\\s?,\\s?SVG\\s?1\\.1\\s?,\\s?MathML\\s?3\\.0(\\s?,\\s?((ITS\\s?2\\.0)|(RDFa\\s?Lite\\s?1\\.1)))?$'
+const html4Expression =
+  '^HTML\\s?4\\.01\\s?(Strict|Transitional|Frameset)\\s?,\\s?URL\\s?\\/\\s?XHTML\\s?1\\.0\\s?(Strict|Transitional|Frameset)\\s?,\\s?URL$'
+const xhtmlExpression =
+  '^(XHTML\\s?,\\s?SVG\\s?1\\.1\\s?,\\s?MathML\\s?3\\.0(\\s?,\\s?RDFa\\s?Lite\\s?1\\.1)?)|(XHTML\\s?1\\.0\\s?Strict\\s?,\\s?URL\\s?,\\s?Ruby\\s?,\\s?SVG\\s?1\\.1\\s?,\\s?MathML\\s?3\\.0)$'
+const svgExpression =
+  '^SVG\\s?1\\.1\\s?,\\s?URL\\s?,\\s?XHTML\\s?,\\s?MathML\\s?3\\.0$'
+const presetRegex = new RegExp(
+  `(${html5Expression})|(${html4Expression})|(${xhtmlExpression})|(${svgExpression})`,
+  'i'
+)
+
+const getMessage = messageTypes => {
+  const messageTypeKeys = Object.keys(messageTypes)
+  messageTypeKeys.sort() // Sort to make the order error, warning for display
+
+  if (messageTypeKeys.length === 0) {
+    return 'validated'
+  }
+
+  const messages = messageTypeKeys.map(
+    key => `${messageTypes[key]} ${key}${messageTypes[key] > 1 ? 's' : ''}`
+  )
+  return messages.join(', ')
+}
+
+const getColor = messageTypes => {
+  if ('error' in messageTypes) {
+    return 'red'
+  }
+
+  if ('warning' in messageTypes) {
+    return 'yellow'
+  }
+
+  return 'brightgreen'
+}
+
+const getSchema = preset => {
+  if (!preset) return undefined
+  const decodedPreset = decodeURI(preset)
+  const schema = []
+  if (new RegExp(html4Expression, 'i').test(decodedPreset)) {
+    if (/Strict/i.test(decodedPreset)) {
+      schema.push('http://s.validator.nu/xhtml10/xhtml-strict.rnc')
+    } else if (/Transitional/i.test(decodedPreset)) {
+      schema.push('http://s.validator.nu/xhtml10/xhtml-transitional.rnc')
+    } else {
+      schema.push('http://s.validator.nu/xhtml10/xhtml-frameset.rnc')
+    }
+    schema.push('http://c.validator.nu/all-html4/')
+  } else if (/1\.0 Strict, URL, Ruby, SVG 1\.1/i.test(decodedPreset)) {
+    schema.push('http://s.validator.nu/xhtml1-ruby-rdf-svg-mathml.rnc')
+    schema.push('http://c.validator.nu/all-html4/')
+  } else {
+    if (new RegExp(html5Expression, 'i').test(decodedPreset)) {
+      if (/ITS 2\.0/i.test(decodedPreset)) {
+        schema.push('http://s.validator.nu/html5-its.rnc')
+      } else if (/RDFa Lite 1\.1/i.test(decodedPreset)) {
+        schema.push('http://s.validator.nu/html5-rdfalite.rnc')
+      } else {
+        schema.push('http://s.validator.nu/html5.rnc')
+      }
+    } else if (new RegExp(xhtmlExpression, 'i').test(decodedPreset)) {
+      if (/RDFa Lite 1\.1/i.test(decodedPreset)) {
+        schema.push('http://s.validator.nu/xhtml5-rdfalite.rnc')
+      } else {
+        schema.push('http://s.validator.nu/xhtml5.rnc')
+      }
+    } else if (new RegExp(svgExpression, 'i').test(decodedPreset)) {
+      schema.push('http://s.validator.nu/svg-xhtml5-rdf-mathml.rnc')
+    }
+    schema.push('http://s.validator.nu/html5/assertions.sch')
+    schema.push('http://c.validator.nu/all/')
+  }
+  return schema.map(url => encodeURI(url)).join(' ')
+}
+
+const documentation = `
+  <style>
+    .box {
+      display: flex;
+      justify-content: space-between;
+    }
+    .note {
+      font-size: smaller;
+      text-align: left;
+    }
+  </style>
+  <p>
+    The W3C validation badge performs validation of the HTML, SVG, MathML, ITS, RDFa Lite, XHTML documents.
+    The badge uses the type property of each message found in the messages from the validation results to determine to be an error or warning.
+    The rules are as follows:
+    <ul class="note">
+      <li>info:  These messages are counted as warnings</li>
+      <li>error:  These messages are counted as errors</li>
+      <li>non-document-error: These messages are counted as errors</li>
+    </ul>
+  </p>
+  <p>
+    This badge relies on the https://validator.nu/ service to perform the validation. Please refer to https://about.validator.nu/ for the full documentation and Terms of service.
+    The following are required from the consumer for the badge to function.
+
+    <ul class="note">
+      <li>
+        Path:
+        <ul>  
+          <li>
+            parser: The parser that is used for validation. This is a passthru value to the service
+            <ul>
+              <li>default <i>(This will not pass a parser to the API and make the API choose the parser based on the validated content)</i></li>
+              <li>html <i>(HTML)</i></li>
+              <li>xml <i>(XML; don’t load external entities)</i></li>
+              <li>xmldtd <i>(XML; load external entities)</i></li>
+            </ul>
+          </li>  
+        </ul>        
+      </li>
+      <li>
+        Query string:
+        <ul>
+          <li>
+            targetUrl (Required): This is the path for the document to be validated
+          </li>
+          <li>
+            preset (Optional can be left as blank): This is used to determine the schema for the document to be valdiated against.
+            The following are the allowed values
+            <ul>
+              <li>HTML, SVG 1.1, MathML 3.0</li>
+              <li>HTML, SVG 1.1, MathML 3.0, ITS 2.0</li>
+              <li>HTML, SVG 1.1, MathML 3.0, RDFa Lite 1.1</li>
+              <li>HTML 4.01 Strict, URL / XHTML 1.0 Strict, URL</li>
+              <li>HTML 4.01 Transitional, URL / XHTML 1.0 Transitional, URL</li>
+              <li>HTML 4.01 Frameset, URL / XHTML 1.0 Frameset, URL</li>
+              <li>XHTML, SVG 1.1, MathML 3.0</li>
+              <li>XHTML, SVG 1.1, MathML 3.0, RDFa Lite 1.1</li>
+              <li>XHTML 1.0 Strict, URL, Ruby, SVG 1.1, MathML 3.0</li>
+              <li>SVG 1.1, URL, XHTML, MathML 3.0</li>        
+            </ul>
+          </li>
+        </ul>      
+      </li>
+    </ul>
+  </p>
+`
+
+module.exports = {
+  documentation,
+  presetRegex,
+  getColor,
+  getMessage,
+  getSchema,
+}
diff --git a/services/w3c/w3c-validation-helper.spec.js b/services/w3c/w3c-validation-helper.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..b5c98ed85283ff9afce8c9da7f0bbacc74c7ab61
--- /dev/null
+++ b/services/w3c/w3c-validation-helper.spec.js
@@ -0,0 +1,265 @@
+'use strict'
+const { expect } = require('chai')
+const { test, given, forCases } = require('sazerac')
+const {
+  presetRegex,
+  getMessage,
+  getColor,
+  getSchema,
+} = require('./w3c-validation-helper')
+
+describe('w3c-validation-helper', function() {
+  describe('presetRegex', function() {
+    function testing(preset) {
+      return presetRegex.test(preset)
+    }
+
+    test(testing, () => {
+      forCases([
+        given('html,svg 1.1,mathml 3.0'),
+        given('HTML,SVG 1.1,MathML 3.0'),
+        given('HTML, SVG 1.1, MathML 3.0'),
+        given('HTML , SVG 1.1 , MathML 3.0'),
+        given('HTML,SVG 1.1,MathML 3.0,ITS 2.0'),
+        given('HTML, SVG 1.1, MathML 3.0, ITS 2.0'),
+        given('HTML , SVG 1.1 , MathML 3.0 , ITS 2.0'),
+        given('HTML,SVG 1.1,MathML 3.0,RDFa Lite 1.1'),
+        given('HTML, SVG 1.1, MathML 3.0, RDFa Lite 1.1'),
+        given('HTML , SVG 1.1 , MathML 3.0 , RDFa Lite 1.1'),
+        given('HTML 4.01 Strict,URL/XHTML 1.0 Strict,URL'),
+        given('HTML 4.01 Strict, URL/ XHTML 1.0 Strict, URL'),
+        given('HTML 4.01 Strict , URL / XHTML 1.0 Strict , URL'),
+        given('HTML 4.01 Transitional,URL/XHTML 1.0 Transitional,URL'),
+        given('HTML 4.01 Transitional, URL/ XHTML 1.0 Transitional, URL'),
+        given('HTML 4.01 Transitional , URL / XHTML 1.0 Transitional , URL'),
+        given('HTML 4.01 Frameset,URL/XHTML 1.0 Frameset,URL'),
+        given('HTML 4.01 Frameset, URL/ XHTML 1.0 Frameset, URL'),
+        given('HTML 4.01 Frameset , URL / XHTML 1.0 Frameset , URL'),
+        given('XHTML,SVG 1.1,MathML 3.0'),
+        given('XHTML, SVG 1.1, MathML 3.0'),
+        given('XHTML , SVG 1.1 , MathML 3.0'),
+        given('XHTML,SVG 1.1,MathML 3.0,RDFa Lite 1.1'),
+        given('XHTML, SVG 1.1, MathML 3.0, RDFa Lite 1.1'),
+        given('XHTML , SVG 1.1 , MathML 3.0 , RDFa Lite 1.1'),
+        given('XHTML 1.0 Strict,URL,Ruby,SVG 1.1,MathML 3.0'),
+        given('XHTML 1.0 Strict, URL, Ruby, SVG 1.1, MathML 3.0'),
+        given('XHTML 1.0 Strict , URL , Ruby , SVG 1.1 , MathML 3.0'),
+        given('SVG 1.1,URL,XHTML,MathML 3.0'),
+        given('SVG 1.1, URL, XHTML, MathML 3.0'),
+        given('SVG 1.1 , URL , XHTML , MathML 3.0'),
+      ]).expect(true)
+    })
+
+    test(testing, () => {
+      forCases([
+        given(undefined),
+        given(null),
+        given(''),
+        given('   '),
+        given('HTML'),
+      ]).expect(false)
+    })
+  })
+
+  describe('getColor', function() {
+    it('returns "brightgreen" if no messages are provided', function() {
+      const messageTypes = {}
+
+      const actualResult = getColor(messageTypes)
+
+      expect(actualResult).to.equal('brightgreen')
+    })
+
+    it('returns "yellow" if only warning messages are provided', function() {
+      const messageTypes = { warning: 1 }
+
+      const actualResult = getColor(messageTypes)
+
+      expect(actualResult).to.equal('yellow')
+    })
+
+    it('returns "red" if only error messages are provided', function() {
+      const messageTypes = { error: 1 }
+
+      const actualResult = getColor(messageTypes)
+
+      expect(actualResult).to.equal('red')
+    })
+
+    it('returns "red" if both warning and error messages are provided', function() {
+      const messageTypes = { warning: 3, error: 4 }
+
+      const actualResult = getColor(messageTypes)
+
+      expect(actualResult).to.equal('red')
+    })
+  })
+
+  describe('getMessage', function() {
+    it('returns "validate" if no messages are provided', function() {
+      const messageTypes = {}
+
+      const actualResult = getMessage(messageTypes)
+
+      expect(actualResult).to.equal('validated')
+    })
+
+    it('returns "1 error" if 1 error message is provided', function() {
+      const messageTypes = { error: 1 }
+
+      const actualResult = getMessage(messageTypes)
+
+      expect(actualResult).to.equal('1 error')
+    })
+
+    it('returns "2 errors" if 2 error messages are provided', function() {
+      const messageTypes = { error: 2 }
+
+      const actualResult = getMessage(messageTypes)
+
+      expect(actualResult).to.equal('2 errors')
+    })
+
+    it('returns "1 warning" if 1 warning message is provided', function() {
+      const messageTypes = { warning: 1 }
+
+      const actualResult = getMessage(messageTypes)
+
+      expect(actualResult).to.equal('1 warning')
+    })
+
+    it('returns "2 warnings" if 2 warning messages are provided', function() {
+      const messageTypes = { warning: 2 }
+
+      const actualResult = getMessage(messageTypes)
+
+      expect(actualResult).to.equal('2 warnings')
+    })
+
+    it('returns "1 error, 1 warning" if 1 error and 1 warning message is provided', function() {
+      const messageTypes = { warning: 1, error: 1 }
+
+      const actualResult = getMessage(messageTypes)
+
+      expect(actualResult).to.equal('1 error, 1 warning')
+    })
+
+    it('returns "2 errors, 2 warnings" if 2 error and 2 warning message is provided', function() {
+      const messageTypes = { error: 2, warning: 2 }
+
+      const actualResult = getMessage(messageTypes)
+
+      expect(actualResult).to.equal('2 errors, 2 warnings')
+    })
+  })
+
+  describe('getSchema', function() {
+    function execution(preset) {
+      return getSchema(preset)
+    }
+
+    test(execution, () => {
+      forCases([given(undefined), given(null), given('')]).expect(undefined)
+    })
+
+    it('returns 3 schemas associated to the "HTML,SVG 1.1,MathML 3.0" preset', function() {
+      const preset = 'HTML,SVG 1.1,MathML 3.0'
+
+      const actualResult = getSchema(preset)
+
+      expect(actualResult).to.equal(
+        'http://s.validator.nu/html5.rnc http://s.validator.nu/html5/assertions.sch http://c.validator.nu/all/'
+      )
+    })
+
+    it('returns 3 schemas associated to the "HTML,SVG 1.1,MathML 3.0,ITS 2.0" preset', function() {
+      const preset = 'HTML,SVG 1.1,MathML 3.0,ITS 2.0'
+
+      const actualResult = getSchema(preset)
+
+      expect(actualResult).to.equal(
+        'http://s.validator.nu/html5-its.rnc http://s.validator.nu/html5/assertions.sch http://c.validator.nu/all/'
+      )
+    })
+
+    it('returns 3 schemas associated to the "HTML, SVG 1.1, MathML 3.0, RDFa Lite 1.1" preset', function() {
+      const preset = 'HTML, SVG 1.1, MathML 3.0, RDFa Lite 1.1'
+
+      const actualResult = getSchema(preset)
+
+      expect(actualResult).to.equal(
+        'http://s.validator.nu/html5-rdfalite.rnc http://s.validator.nu/html5/assertions.sch http://c.validator.nu/all/'
+      )
+    })
+
+    it('returns 3 schemas associated to the "HTML 4.01 Strict, URL/ XHTML 1.0 Strict, URL" preset', function() {
+      const preset = 'HTML 4.01 Strict, URL/ XHTML 1.0 Strict, URL'
+
+      const actualResult = getSchema(preset)
+
+      expect(actualResult).to.equal(
+        'http://s.validator.nu/xhtml10/xhtml-strict.rnc http://c.validator.nu/all-html4/'
+      )
+    })
+
+    it('returns 3 schemas associated to the "HTML 4.01 Transitional, URL/ XHTML 1.0 Transitional, URL" preset', function() {
+      const preset = 'HTML 4.01 Transitional, URL/ XHTML 1.0 Transitional, URL'
+
+      const actualResult = getSchema(preset)
+
+      expect(actualResult).to.equal(
+        'http://s.validator.nu/xhtml10/xhtml-transitional.rnc http://c.validator.nu/all-html4/'
+      )
+    })
+
+    it('returns 3 schemas associated to the "HTML 4.01 Frameset, URL/ XHTML 1.0 Frameset, URL" preset', function() {
+      const preset = 'HTML 4.01 Frameset, URL/ XHTML 1.0 Frameset, URL'
+
+      const actualResult = getSchema(preset)
+
+      expect(actualResult).to.equal(
+        'http://s.validator.nu/xhtml10/xhtml-frameset.rnc http://c.validator.nu/all-html4/'
+      )
+    })
+
+    it('returns 3 schemas associated to the "XHTML, SVG 1.1, MathML 3.0" preset', function() {
+      const preset = 'XHTML, SVG 1.1, MathML 3.0'
+
+      const actualResult = getSchema(preset)
+
+      expect(actualResult).to.equal(
+        'http://s.validator.nu/xhtml5.rnc http://s.validator.nu/html5/assertions.sch http://c.validator.nu/all/'
+      )
+    })
+
+    it('returns 3 schemas associated to the "XHTML, SVG 1.1, MathML 3.0, RDFa Lite 1.1" preset', function() {
+      const preset = 'XHTML, SVG 1.1, MathML 3.0, RDFa Lite 1.1'
+
+      const actualResult = getSchema(preset)
+
+      expect(actualResult).to.equal(
+        'http://s.validator.nu/xhtml5-rdfalite.rnc http://s.validator.nu/html5/assertions.sch http://c.validator.nu/all/'
+      )
+    })
+
+    it('returns 3 schemas associated to the "XHTML 1.0 Strict, URL, Ruby, SVG 1.1, MathML 3.0" preset', function() {
+      const preset = 'XHTML 1.0 Strict, URL, Ruby, SVG 1.1, MathML 3.0'
+
+      const actualResult = getSchema(preset)
+
+      expect(actualResult).to.equal(
+        'http://s.validator.nu/xhtml1-ruby-rdf-svg-mathml.rnc http://c.validator.nu/all-html4/'
+      )
+    })
+
+    it('returns 3 schemas associated to the "SVG 1.1, URL, XHTML, MathML 3.0" preset', function() {
+      const preset = 'SVG 1.1, URL, XHTML, MathML 3.0'
+
+      const actualResult = getSchema(preset)
+
+      expect(actualResult).to.equal(
+        'http://s.validator.nu/svg-xhtml5-rdf-mathml.rnc http://s.validator.nu/html5/assertions.sch http://c.validator.nu/all/'
+      )
+    })
+  })
+})
diff --git a/services/w3c/w3c-validation.service.js b/services/w3c/w3c-validation.service.js
new file mode 100644
index 0000000000000000000000000000000000000000..474f98295628e9f08972fcea6cc53d5fefa73be1
--- /dev/null
+++ b/services/w3c/w3c-validation.service.js
@@ -0,0 +1,136 @@
+'use strict'
+const Joi = require('@hapi/joi')
+const { optionalUrl } = require('../validators')
+const {
+  documentation,
+  presetRegex,
+  getColor,
+  getMessage,
+  getSchema,
+} = require('./w3c-validation-helper')
+const { BaseJsonService, NotFound } = require('..')
+
+const schema = Joi.object({
+  url: Joi.string().optional(),
+  messages: Joi.array()
+    .required()
+    .items(
+      Joi.object({
+        type: Joi.string()
+          .allow('info', 'error', 'non-document-error')
+          .required(),
+        subType: Joi.string().optional(),
+        message: Joi.string().required(),
+      })
+    ),
+}).required()
+
+const queryParamSchema = Joi.object({
+  targetUrl: optionalUrl.required(),
+  preset: Joi.string()
+    .regex(presetRegex)
+    .allow(''),
+}).required()
+
+module.exports = class W3cValidation extends BaseJsonService {
+  static get category() {
+    return 'analysis'
+  }
+
+  static get route() {
+    return {
+      base: 'w3c-validation',
+      pattern: ':parser(default|html|xml|xmldtd)',
+      queryParamSchema,
+    }
+  }
+
+  static get examples() {
+    return [
+      {
+        title: 'W3C Validation',
+        namedParams: { parser: 'html' },
+        queryParams: {
+          targetUrl: 'https://validator.nu/',
+          preset: 'HTML, SVG 1.1, MathML 3.0',
+        },
+        staticPreview: this.render({ messageTypes: {} }),
+        documentation,
+      },
+    ]
+  }
+
+  static get defaultBadgeData() {
+    return {
+      label: 'w3c',
+    }
+  }
+
+  static render({ messageTypes }) {
+    return {
+      message: getMessage(messageTypes),
+      color: getColor(messageTypes),
+    }
+  }
+
+  async fetch(targetUrl, preset, parser) {
+    return this._requestJson({
+      url: 'https://validator.nu/',
+      schema,
+      options: {
+        qs: {
+          schema: getSchema(preset),
+          parser: parser === 'default' ? undefined : parser,
+          doc: encodeURI(targetUrl),
+          out: 'json',
+        },
+      },
+    })
+  }
+
+  transform(url, messages) {
+    if (messages.length === 1) {
+      const { subType, type, message } = messages[0]
+      if (type === 'non-document-error' && subType === 'io') {
+        let notFound = false
+        if (
+          message ===
+          'HTTP resource not retrievable. The HTTP status from the remote server was: 404.'
+        ) {
+          notFound = true
+        } else if (message.endsWith('Name or service not known')) {
+          const domain = message.split(':')[0].trim()
+          notFound = url.indexOf(domain) !== -1
+        }
+
+        if (notFound) {
+          throw new NotFound({ prettyMessage: 'target url not found' })
+        }
+      }
+    }
+
+    return messages.reduce((accumulator, message) => {
+      let { type } = message
+      if (type === 'info') {
+        type = 'warning'
+      } else {
+        // All messages are suppose to have a type and there can only be info, error or non-document
+        // If a new type gets introduce this will flag them as errors
+        type = 'error'
+      }
+
+      if (!(type in accumulator)) {
+        accumulator[type] = 0
+      }
+      accumulator[type] += 1
+      return accumulator
+    }, {})
+  }
+
+  async handle({ parser }, { targetUrl, preset }) {
+    const { url, messages } = await this.fetch(targetUrl, preset, parser)
+    return this.constructor.render({
+      messageTypes: this.transform(url, messages),
+    })
+  }
+}
diff --git a/services/w3c/w3c-validation.tester.js b/services/w3c/w3c-validation.tester.js
new file mode 100644
index 0000000000000000000000000000000000000000..b2eff4457c3b4019c4f522e195948b535c08ab40
--- /dev/null
+++ b/services/w3c/w3c-validation.tester.js
@@ -0,0 +1,100 @@
+'use strict'
+const Joi = require('@hapi/joi')
+const t = (module.exports = require('../tester').createServiceTester())
+
+const isErrorOnly = Joi.string().regex(/^[0-9]+ errors?$/)
+
+const isWarningOnly = Joi.string().regex(/^[0-9]+ warnings?$/)
+
+const isErrorAndWarning = Joi.string().regex(
+  /^[0-9]+ errors?, [0-9]+ warnings?$/
+)
+
+const isW3CMessage = Joi.alternatives().try(
+  'validated',
+  isErrorOnly,
+  isWarningOnly,
+  isErrorAndWarning
+)
+const isW3CColors = Joi.alternatives().try('brightgreen', 'red', 'yellow')
+t.create(
+  'W3C Validation page conforms to standards with no preset and parser with brightgreen badge'
+)
+  .get(
+    '/default.json?targetUrl=https://hsivonen.com/test/moz/messages-types/no-message.html'
+  )
+  .expectBadge({
+    label: 'w3c',
+    message: isW3CMessage,
+    color: isW3CColors,
+  })
+
+t.create(
+  'W3C Validation page conforms to standards with no HTML4 preset and HTML parser with brightgreen badge'
+)
+  .get(
+    '/html.json?targetUrl=https://hsivonen.com/test/moz/messages-types/no-message.html&preset=HTML,%20SVG%201.1,%20MathML%203.0'
+  )
+  .expectBadge({
+    label: 'w3c',
+    message: isW3CMessage,
+    color: isW3CColors,
+  })
+
+t.create('W3C Validation target url not found error')
+  .get(
+    '/default.json?targetUrl=http://hsivonen.com/test/moz/messages-types/404.html'
+  )
+  .expectBadge({
+    label: 'w3c',
+    message: 'target url not found',
+  })
+
+t.create('W3C Validation target url host not found error')
+  .get('/default.json?targetUrl=https://adfasdfasdfasdfadfadfadfasdfadf.com')
+  .expectBadge({
+    label: 'w3c',
+    message: 'target url not found',
+  })
+
+t.create('W3C Validation page has 1 validation error with red badge')
+  .get(
+    '/default.json?targetUrl=http://hsivonen.com/test/moz/messages-types/warning.html'
+  )
+  .expectBadge({
+    label: 'w3c',
+    message: isW3CMessage,
+    color: isW3CColors,
+  })
+
+t.create(
+  'W3C Validation page has 3 validation error using HTML 4.01 Frameset preset with red badge'
+)
+  .get(
+    '/html.json?targetUrl=http://hsivonen.com/test/moz/messages-types/warning.html&preset=HTML 4.01 Frameset, URL / XHTML 1.0 Frameset, URL'
+  )
+  .expectBadge({
+    label: 'w3c',
+    message: isW3CMessage,
+    color: isW3CColors,
+  })
+
+t.create('W3C Validation page has 1 validation warning with yellow badge')
+  .get(
+    '/default.json?targetUrl=http://hsivonen.com/test/moz/messages-types/info.svg'
+  )
+  .expectBadge({
+    label: 'w3c',
+    message: isW3CMessage,
+    color: isW3CColors,
+  })
+
+t.create('W3C Validation page has multiple of validation errors with red badge')
+  .get(
+    '/default.json?targetUrl=http://hsivonen.com/test/moz/messages-types/range-error.html'
+  )
+  .expectBadge({
+    label: 'w3c',
+    message: isW3CMessage,
+    color: isW3CColors,
+  })