diff --git a/app/__init__.py b/app/__init__.py
index 3a1f54fc14b5e002a08fc4a31be30f8a502f0bd8..aef0c2b69986b21ead737e6326c7673e1162ff1c 100644
--- a/app/__init__.py
+++ b/app/__init__.py
@@ -48,4 +48,7 @@ def create_app(config_name):
     from .services import services as services_blueprint
     app.register_blueprint(services_blueprint, url_prefix='/services')
 
+    from .test import test as test_blueprint
+    app.register_blueprint(test_blueprint, url_prefix='/test')
+
     return app
diff --git a/app/templates/test/index.html.j2 b/app/templates/test/index.html.j2
new file mode 100644
index 0000000000000000000000000000000000000000..1dc49c1ed123b0c6624f8f473c885a4652de4f5e
--- /dev/null
+++ b/app/templates/test/index.html.j2
@@ -0,0 +1,84 @@
+{% extends "limited_width.html.j2" %}
+
+{% block page_content %}
+<div class="col s12">
+  <h3>Submit a Test</h3>
+</div>
+
+<div class="col s12">
+  <div class="card">
+    <form id="test-form">
+      {{ test_form.hidden_tag() }}
+      <div class="card-content">
+        <div class="row">
+          <div class="col s12 m4">
+            <div class="input-field">
+              <i class="material-icons prefix">title</i>
+              {{ test_form.title(data_length='32') }}
+              {{ test_form.title.label }}
+            </div>
+          </div>
+          <div class="col s12 m8">
+            <div class="input-field">
+              <i class="material-icons prefix">description</i>
+              {{ test_form.description(data_length='255') }}
+              {{ test_form.description.label }}
+            </div>
+          </div>
+          <div class="col s12">
+            <div class="file-field input-field">
+              <div class="btn">
+                <span>{{ test_form.file.label.text }}</span>
+                {{ test_form.file() }}
+              </div>
+              <div class="file-path-wrapper">
+                <input class="file-path validate" type="text">
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+      <div class="card-action right-align">
+        <button class="btn waves-effect waves-light" name="submit" type="submit">Submit<i class="material-icons right">send</i></button>
+      </div>
+    </form>
+  </div>
+</div>
+
+<script>
+var testFormElement = document.getElementById("test-form");
+
+testFormElement.addEventListener("submit", function(event) {
+  event.preventDefault();
+  let csrfTokenElement = testFormElement.querySelector("input[name='csrf_token']"),
+      descriptionElement = testFormElement.querySelector("input[name='description']"),
+      fileElement = testFormElement.querySelector("input[name='file']"),
+      titleElement = testFormElement.querySelector("input[name='title']");
+  let file = fileElement.files[0];
+  let data = {"csrf_token": csrfTokenElement.value,
+              "description": descriptionElement.value,
+              "file": {"bytes": file, "name": file.name},
+              "title": titleElement.value};
+  nopaque.socket.emit("submit-test-form", data);
+});
+
+nopaque.socket.on("submit-test-form", function(response) {
+  console.log(response);
+  if (response.status === "success") {
+     testFormElement.reset();
+     nopaque.toast("Success!");
+     for (let helperText of testFormElement.querySelectorAll(".helper-text")) {
+       helperText.remove();
+     }
+  } else if (response.status === "error") {
+    let errorElement;
+    for (let [field, error] of Object.entries(response.msg)) {
+      errorElement = document.createElement("span");
+      errorElement.classList.add("helper-text", "red-text");
+      errorElement.innerText = error;
+      testFormElement.querySelector(`input[name="${field}"]`).closest(".input-field").appendChild(errorElement);
+    }
+  }
+});
+</script>
+{% endblock %}
diff --git a/app/test/__init__.py b/app/test/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e01f868c5e4021a894996e6bad6923eb252e89c0
--- /dev/null
+++ b/app/test/__init__.py
@@ -0,0 +1,5 @@
+from flask import Blueprint
+
+
+test = Blueprint('test', __name__)
+from . import events, views
diff --git a/app/test/events.py b/app/test/events.py
new file mode 100644
index 0000000000000000000000000000000000000000..3400cd4f21218b0224690b3e336b0392c3939479
--- /dev/null
+++ b/app/test/events.py
@@ -0,0 +1,24 @@
+from app import logger, socketio
+from flask_login import login_required
+from werkzeug.datastructures import FileStorage
+from werkzeug.utils import secure_filename
+from .forms import TestForm
+import io
+
+
+@socketio.on('submit-test-form')
+@login_required
+def recv_test_form(data):
+    filename = secure_filename(data['file']['name'])
+    stream = io.BytesIO(data['file']['bytes'])
+    file = FileStorage(stream=stream, filename=filename)
+    data['file'] = file
+    test_form = TestForm(data=data)
+    if test_form.validate():
+        logger.warning('test_form is ok!')
+        socketio.emit('submit-test-form', {'status': 'success',
+                                           'msg': 'test_form is ok!'})
+    else:
+        logger.warning('test_form is not ok!')
+        socketio.emit('submit-test-form', {'status': 'error',
+                                           'msg': test_form.errors})
diff --git a/app/test/forms.py b/app/test/forms.py
new file mode 100644
index 0000000000000000000000000000000000000000..b7dd19d81bed90c6e0a9ef05efabf9d87ca78181
--- /dev/null
+++ b/app/test/forms.py
@@ -0,0 +1,12 @@
+from flask_wtf import FlaskForm
+from flask_wtf.file import FileAllowed, FileField, FileRequired
+from wtforms import StringField, SubmitField
+from wtforms.validators import DataRequired, Length
+
+
+class TestForm(FlaskForm):
+    description = StringField('Description',
+                              validators=[DataRequired(), Length(1, 255)])
+    file = FileField('File', validators=[FileAllowed(['txt'], 'Plain text only!'), FileRequired()])
+    submit = SubmitField()
+    title = StringField('Title', validators=[DataRequired(), Length(1, 32)])
diff --git a/app/test/views.py b/app/test/views.py
new file mode 100644
index 0000000000000000000000000000000000000000..a0e76fc2e00143e004d2302bfb1f2f869830094c
--- /dev/null
+++ b/app/test/views.py
@@ -0,0 +1,12 @@
+from flask import render_template
+from flask_login import login_required
+from . import test
+from .forms import TestForm
+
+
+@test.route('/')
+@login_required
+def index():
+    test_form = TestForm()
+    return render_template('test/index.html.j2', title='Test',
+                           test_form=test_form)