diff --git a/CHANGELOG.md b/CHANGELOG.md
index 79da8400..893ccf61 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -101,3 +101,9 @@ Resulting in a folder structure like the following:
 - Continue an incomplete tutorial started in the same workspace. Choose the "continue" path from the start screen. Progress is stored in local storage in the workspace.
 
 ![continue tutorial](./docs/images/continue-tutorial.png)
+
+## [0.5.0]
+
+- Show error messages in the webview UI
+
+![fail message in webview](./docs/images/fail-message-in-webview.png)
diff --git a/docs/images/fail-message-in-webview.png b/docs/images/fail-message-in-webview.png
new file mode 100644
index 00000000..c4d5488e
Binary files /dev/null and b/docs/images/fail-message-in-webview.png differ
diff --git a/src/channel/index.ts b/src/channel/index.ts
index 07e4298f..fd7c09cf 100644
--- a/src/channel/index.ts
+++ b/src/channel/index.ts
@@ -14,6 +14,7 @@ import { openWorkspace, checkWorkspaceEmpty } from '../services/workspace'
 import { readFile } from 'fs'
 import { join } from 'path'
 import { promisify } from 'util'
+import { showOutput } from '../services/testRunner/output'
 import { WORKSPACE_ROOT } from '../environment'
 
 const readFileAsync = promisify(readFile)
@@ -300,7 +301,9 @@ class Channel implements Channel {
         // update progress when a level is deemed complete in the client
         await this.context.progress.syncProgress(action.payload.progress)
         return
-
+      case 'EDITOR_OPEN_LOGS':
+        const channel = action.payload.channel
+        await showOutput(channel)
       default:
         logger(`No match for action type: ${actionType}`)
         return
diff --git a/src/editor/commands.ts b/src/editor/commands.ts
index 19b96585..002b8211 100644
--- a/src/editor/commands.ts
+++ b/src/editor/commands.ts
@@ -3,7 +3,7 @@ import * as TT from 'typings/tutorial'
 import * as vscode from 'vscode'
 import createTestRunner from '../services/testRunner'
 import { setupActions } from '../actions/setupActions'
-import createWebView from '../webview'
+import createWebView from '../services/webview'
 import logger from '../services/logger'
 
 export const COMMANDS = {
@@ -62,9 +62,9 @@ export const createCommands = ({ extensionPath, workspaceState }: CreateCommandP
           // send test pass message back to client
           webview.send({ type: 'TEST_PASS', payload: { position } })
         },
-        onFail: (position: T.Position, message: string) => {
+        onFail: (position: T.Position, failSummary: T.TestFail): void => {
           // send test fail message back to client with failure message
-          webview.send({ type: 'TEST_FAIL', payload: { position, message } })
+          webview.send({ type: 'TEST_FAIL', payload: { position, fail: failSummary } })
         },
         onError: (position: T.Position) => {
           // TODO: send test error message back to client
diff --git a/src/environment.ts b/src/environment.ts
index 89a61125..6fd41f4d 100644
--- a/src/environment.ts
+++ b/src/environment.ts
@@ -10,7 +10,7 @@ export type Env = 'test' | 'local' | 'development' | 'production'
 export const NODE_ENV: Env = process.env.NODE_ENV || 'production'
 
 // toggle logging in development
-export const LOG = false
+export const LOG = true
 
 // error logging tool
 export const SENTRY_DSN: string | null = null
diff --git a/src/services/testRunner/formatOutput.ts b/src/services/testRunner/formatOutput.ts
index 00a2d8dc..2f538df4 100644
--- a/src/services/testRunner/formatOutput.ts
+++ b/src/services/testRunner/formatOutput.ts
@@ -4,7 +4,7 @@ import { ParserOutput, Fail } from './parser'
 // export const formatSuccessOutput = (tap: ParserOutput): string => {}
 
 export const formatFailOutput = (tap: ParserOutput): string => {
-  let output = `FAILED TESTS\n`
+  let output = `FAILED TEST LOG\n`
   tap.failed.forEach((fail: Fail) => {
     const details = fail.details ? `\n${fail.details}\n` : ''
     const logs = fail.logs ? `\n${fail.logs.join('\n')}\n` : ''
diff --git a/src/services/testRunner/index.ts b/src/services/testRunner/index.ts
index 182abc5e..57cea72a 100644
--- a/src/services/testRunner/index.ts
+++ b/src/services/testRunner/index.ts
@@ -5,12 +5,12 @@ import logger from '../logger'
 import parser from './parser'
 import { debounce, throttle } from './throttle'
 import onError from '../sentry/onError'
-import { clearOutput, displayOutput } from './output'
+import { clearOutput, addOutput } from './output'
 import { formatFailOutput } from './formatOutput'
 
 interface Callbacks {
   onSuccess(position: T.Position): void
-  onFail(position: T.Position, message: string): void
+  onFail(position: T.Position, failSummary: T.TestFail): void
   onRun(position: T.Position): void
   onError(position: T.Position): void
 }
@@ -51,20 +51,24 @@ const createTestRunner = (config: TT.TutorialTestRunnerConfig, callbacks: Callba
 
     const tap = parser(stdout || '')
 
-    displayOutput({ channel: logChannelName, text: tap.logs.join('\n'), show: false })
+    addOutput({ channel: logChannelName, text: tap.logs.join('\n'), show: false })
 
     if (stderr) {
       // FAIL also trigger stderr
       if (stdout && stdout.length && !tap.ok) {
-        const firstFailMessage = tap.failed[0].message
-        callbacks.onFail(position, firstFailMessage)
+        const firstFail = tap.failed[0]
+        const failSummary = {
+          title: firstFail.message || 'Test Failed',
+          description: firstFail.details || 'Unknown error',
+        }
+        callbacks.onFail(position, failSummary)
         const output = formatFailOutput(tap)
-        displayOutput({ channel: failChannelName, text: output, show: true })
+        addOutput({ channel: failChannelName, text: output, show: true })
         return
       } else {
         callbacks.onError(position)
         // open terminal with error string
-        displayOutput({ channel: failChannelName, text: stderr, show: true })
+        addOutput({ channel: failChannelName, text: stderr, show: true })
         return
       }
     }
diff --git a/src/services/testRunner/output.ts b/src/services/testRunner/output.ts
index 916b6000..c390c9ba 100644
--- a/src/services/testRunner/output.ts
+++ b/src/services/testRunner/output.ts
@@ -9,22 +9,25 @@ const getOutputChannel = (name: string): vscode.OutputChannel => {
   return channels[name]
 }
 
-interface DisplayOutput {
+interface ChannelOutput {
   channel: string
   text: string
   show?: boolean
 }
 
-export const displayOutput = (params: DisplayOutput) => {
+export const addOutput = (params: ChannelOutput) => {
   const channel = getOutputChannel(params.channel)
   channel.clear()
-  channel.show(params.show || false)
   channel.append(params.text)
 }
 
+export const showOutput = (channelName: string) => {
+  const channel = getOutputChannel(channelName)
+  channel.show()
+}
+
 export const clearOutput = (channelName: string) => {
   const channel = getOutputChannel(channelName)
-  channel.show(false)
   channel.clear()
   channel.hide()
 }
diff --git a/src/webview/index.ts b/src/services/webview/index.ts
similarity index 98%
rename from src/webview/index.ts
rename to src/services/webview/index.ts
index e6fd4a3e..bfd27ef9 100644
--- a/src/webview/index.ts
+++ b/src/services/webview/index.ts
@@ -1,7 +1,7 @@
 import * as path from 'path'
 import { Action } from 'typings'
 import * as vscode from 'vscode'
-import Channel from '../channel'
+import Channel from '../../channel'
 import render from './render'
 
 interface ReactWebViewProps {
diff --git a/src/webview/render.ts b/src/services/webview/render.ts
similarity index 98%
rename from src/webview/render.ts
rename to src/services/webview/render.ts
index 1bf5bf6a..63b2680f 100644
--- a/src/webview/render.ts
+++ b/src/services/webview/render.ts
@@ -1,7 +1,7 @@
 import { JSDOM } from 'jsdom'
 import * as path from 'path'
 import * as vscode from 'vscode'
-import onError from '../services/sentry/onError'
+import onError from '../sentry/onError'
 
 const getNonce = (): string => {
   let text = ''
diff --git a/typings/index.d.ts b/typings/index.d.ts
index 78c3d501..a80e8e00 100644
--- a/typings/index.d.ts
+++ b/typings/index.d.ts
@@ -42,6 +42,7 @@ export interface TestStatus {
   type: 'success' | 'warning' | 'error' | 'loading'
   title: string
   content?: string
+  timeout?: number
 }
 
 export interface MachineContext {
@@ -116,3 +117,8 @@ export interface ProcessEvent {
   description: string
   status: 'RUNNING' | 'SUCCESS' | 'FAIL' | 'ERROR'
 }
+
+export type TestFail = {
+  title: string
+  description: string
+}
diff --git a/web-app/src/components/Message/index.tsx b/web-app/src/components/Message/index.tsx
index c0f428d2..8d3ccc05 100644
--- a/web-app/src/components/Message/index.tsx
+++ b/web-app/src/components/Message/index.tsx
@@ -11,6 +11,7 @@ interface Props {
   closeable?: boolean
   onClose?: () => void
   handleClose?: () => void
+  children?: React.ReactElement | null
 }
 
 const Message = (props: Props) => {
@@ -30,7 +31,10 @@ const Message = (props: Props) => {
       onClose={onClose}
       shape={props.shape}
     >
-      {props.content}
+      <div>
+        <div>{props.content}</div>
+        <div>{props.children}</div>
+      </div>
     </AlifdMessage>
   )
 }
diff --git a/web-app/src/components/ProcessMessages/TestMessage.tsx b/web-app/src/components/ProcessMessages/TestMessage.tsx
index 57b7eb2e..2eebaa3e 100644
--- a/web-app/src/components/ProcessMessages/TestMessage.tsx
+++ b/web-app/src/components/ProcessMessages/TestMessage.tsx
@@ -5,7 +5,7 @@ import { css, jsx } from '@emotion/core'
 
 const durations = {
   success: 1000,
-  warning: 4500,
+  warning: 20000,
   error: 4500,
   loading: 300000,
 }
@@ -24,7 +24,7 @@ const useTimeout = ({ duration, key }: { duration: number; key: string }) => {
   return timeoutClose
 }
 
-const TestMessage = (props: T.TestStatus) => {
+const TestMessage = (props: T.TestStatus & { children?: React.ReactElement | null }) => {
   const duration = durations[props.type]
   const timeoutClose = useTimeout({ duration, key: props.title })
   return (
@@ -36,7 +36,9 @@ const TestMessage = (props: T.TestStatus) => {
       size="medium"
       closeable={props.type !== 'loading'}
       content={props.content}
-    />
+    >
+      {props.children}
+    </Message>
   )
 }
 
diff --git a/web-app/src/components/ProcessMessages/index.tsx b/web-app/src/components/ProcessMessages/index.tsx
index c1614c40..80334568 100644
--- a/web-app/src/components/ProcessMessages/index.tsx
+++ b/web-app/src/components/ProcessMessages/index.tsx
@@ -1,12 +1,14 @@
 import Message from '../Message'
 import * as React from 'react'
 import * as T from 'typings'
+import Button from '../Button'
 import { css, jsx } from '@emotion/core'
 import TestMessage from './TestMessage'
 
 interface Props {
   testStatus?: T.TestStatus | null
   processes: T.ProcessEvent[]
+  onOpenLogs?: (channel: string) => void
 }
 
 const styles = {
@@ -17,9 +19,21 @@ const styles = {
 }
 
 // display a list of active processes
-const ProcessMessages = ({ processes, testStatus }: Props) => {
+const ProcessMessages = ({ processes, testStatus, onOpenLogs }: Props) => {
   if (testStatus) {
-    return <TestMessage {...testStatus} />
+    return (
+      <TestMessage {...testStatus}>
+        {testStatus.type === 'warning' ? (
+          <Button
+            onClick={() => onOpenLogs && onOpenLogs('CodeRoad (Tests)')}
+            type="normal"
+            style={{ marginTop: '0.8rem' }}
+          >
+            Open Logs
+          </Button>
+        ) : null}
+      </TestMessage>
+    )
   }
   if (!processes.length) {
     return null
diff --git a/web-app/src/containers/Tutorial/components/Level.tsx b/web-app/src/containers/Tutorial/components/Level.tsx
index 38f7a8bd..2ee7a26b 100644
--- a/web-app/src/containers/Tutorial/components/Level.tsx
+++ b/web-app/src/containers/Tutorial/components/Level.tsx
@@ -96,6 +96,7 @@ interface Props {
   testStatus: T.TestStatus | null
   onContinue(): void
   onLoadSolution(): void
+  onOpenLogs(channel: string): void
 }
 
 const Level = ({
@@ -107,6 +108,7 @@ const Level = ({
   status,
   onContinue,
   onLoadSolution,
+  onOpenLogs,
   processes,
   testStatus,
 }: Props) => {
@@ -170,7 +172,7 @@ const Level = ({
 
         {(testStatus || processes.length > 0) && (
           <div css={styles.processes}>
-            <ProcessMessages processes={processes} testStatus={testStatus} />
+            <ProcessMessages processes={processes} testStatus={testStatus} onOpenLogs={onOpenLogs} />
           </div>
         )}
 
diff --git a/web-app/src/containers/Tutorial/index.tsx b/web-app/src/containers/Tutorial/index.tsx
index 4c04f660..c0dd156f 100644
--- a/web-app/src/containers/Tutorial/index.tsx
+++ b/web-app/src/containers/Tutorial/index.tsx
@@ -32,6 +32,10 @@ const TutorialPage = (props: PageProps) => {
     props.send({ type: 'STEP_SOLUTION_LOAD' })
   }
 
+  const onOpenLogs = (channel: string): void => {
+    props.send({ type: 'OPEN_LOGS', payload: { channel } })
+  }
+
   const steps = levelData.steps.map((step: TT.Step) => {
     // label step status for step component
     let status: T.ProgressStatus = 'INCOMPLETE'
@@ -61,6 +65,7 @@ const TutorialPage = (props: PageProps) => {
       status={progress.levels[position.levelId] ? 'COMPLETE' : 'ACTIVE'}
       onContinue={onContinue}
       onLoadSolution={onLoadSolution}
+      onOpenLogs={onOpenLogs}
       processes={processes}
       testStatus={testStatus}
     />
diff --git a/web-app/src/services/state/actions/editor.ts b/web-app/src/services/state/actions/editor.ts
index e3121018..da0f2e97 100644
--- a/web-app/src/services/state/actions/editor.ts
+++ b/web-app/src/services/state/actions/editor.ts
@@ -1,4 +1,4 @@
-import * as CR from 'typings'
+import * as T from 'typings'
 import * as TT from 'typings/tutorial'
 import * as selectors from '../../selectors'
 
@@ -8,7 +8,7 @@ export default (editorSend: any) => ({
       type: 'EDITOR_STARTUP',
     })
   },
-  configureNewTutorial(context: CR.MachineContext) {
+  configureNewTutorial(context: T.MachineContext) {
     editorSend({
       type: 'EDITOR_TUTORIAL_CONFIG',
       payload: {
@@ -17,7 +17,7 @@ export default (editorSend: any) => ({
       },
     })
   },
-  continueConfig(context: CR.MachineContext) {
+  continueConfig(context: T.MachineContext) {
     editorSend({
       type: 'EDITOR_TUTORIAL_CONTINUE_CONFIG',
       payload: {
@@ -26,7 +26,7 @@ export default (editorSend: any) => ({
       },
     })
   },
-  loadLevel(context: CR.MachineContext): void {
+  loadLevel(context: T.MachineContext): void {
     const level: TT.Level = selectors.currentLevel(context)
     const step: TT.Step | null = selectors.currentStep(context)
     // load step actions
@@ -41,7 +41,7 @@ export default (editorSend: any) => ({
       },
     })
   },
-  loadStep(context: CR.MachineContext): void {
+  loadStep(context: T.MachineContext): void {
     const step: TT.Step | null = selectors.currentStep(context)
     if (step && step.setup) {
       // load step actions
@@ -58,7 +58,7 @@ export default (editorSend: any) => ({
       })
     }
   },
-  editorLoadSolution(context: CR.MachineContext): void {
+  editorLoadSolution(context: T.MachineContext): void {
     const step: TT.Step | null = selectors.currentStep(context)
     // tell editor to load solution commit
     if (step && step.solution) {
@@ -74,7 +74,7 @@ export default (editorSend: any) => ({
       })
     }
   },
-  syncLevelProgress(context: CR.MachineContext): void {
+  syncLevelProgress(context: T.MachineContext): void {
     editorSend({
       type: 'EDITOR_SYNC_PROGRESS',
       payload: {
@@ -95,4 +95,10 @@ export default (editorSend: any) => ({
       type: 'EDITOR_REQUEST_WORKSPACE',
     })
   },
+  editorOpenLogs(context: T.MachineContext, event: T.MachineEvent): void {
+    editorSend({
+      type: 'EDITOR_OPEN_LOGS',
+      payload: { channel: event.payload.channel },
+    })
+  },
 })
diff --git a/web-app/src/services/state/actions/testNotify.ts b/web-app/src/services/state/actions/testNotify.ts
index 5b509adc..c5f7bda3 100644
--- a/web-app/src/services/state/actions/testNotify.ts
+++ b/web-app/src/services/state/actions/testNotify.ts
@@ -20,8 +20,8 @@ const testActions: ActionFunctionMap<CR.MachineContext, CR.MachineEvent> = {
   testFail: assign({
     testStatus: (context, event) => ({
       type: 'warning',
-      title: 'Fail!',
-      content: event.payload.message,
+      title: event.payload.fail.title,
+      content: event.payload.fail.description,
     }),
   }),
   // @ts-ignore
diff --git a/web-app/src/services/state/machine.ts b/web-app/src/services/state/machine.ts
index 8b91d813..ccf7589a 100644
--- a/web-app/src/services/state/machine.ts
+++ b/web-app/src/services/state/machine.ts
@@ -170,6 +170,9 @@ export const createMachine = (options: any) => {
                     STEP_SOLUTION_LOAD: {
                       actions: ['editorLoadSolution'],
                     },
+                    OPEN_LOGS: {
+                      actions: ['editorOpenLogs'],
+                    },
                   },
                 },
                 TestRunning: {
diff --git a/web-app/stories/Checkbox.stories.tsx b/web-app/stories/Checkbox.stories.tsx
index c8c935ca..ae1d1431 100644
--- a/web-app/stories/Checkbox.stories.tsx
+++ b/web-app/stories/Checkbox.stories.tsx
@@ -1,6 +1,5 @@
 import { storiesOf } from '@storybook/react'
 import React from 'react'
-import { css, jsx } from '@emotion/core'
 import Checkbox from '../src/components/Checkbox'
 import SideBarDecorator from './utils/SideBarDecorator'