diff --git a/test/application_system_test_case.rb b/test/application_system_test_case.rb
index 7db232e3dfc51eefdbdfd7f7ae4e962795a110a8..da6f04b0020ef37e3048ae2c01ecef66a3930c89 100644
--- a/test/application_system_test_case.rb
+++ b/test/application_system_test_case.rb
@@ -62,4 +62,42 @@ class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
       users(user_or_fixture)
     end
   end
+
+  # In the post form, this method will select the given tag.
+  #
+  # @param tag_name [String] the name of the tag
+  # @param create_new [Boolean] whether creating a new tag is allowed (default false)
+  def post_form_select_tag(tag_name, create_new = false)
+    # First enter the tag name into the select2 search field for the tag
+    within find_field('Tags (at least one):').find(:xpath, '..') do
+      find('.select2-search__field').fill_in(with: tag_name)
+    end
+
+    # Get the first item listed that is not the "Searching..." item
+    first_option = find('#select2-post_tags_cache-results li:first-child') { |el| el.text != 'Searching…' }
+
+    if first_option.first('span').text == tag_name
+      # If the text matches the tag name, first check whether we are creating a new tag.
+      # If so, confirm that we are allowed to. If all is good, actually click on the item.
+      if create_new || !first_option.text.include?('Create new tag')
+        first_option.click
+      else
+        raise "Expected to find tag with the name #{tag_name}, " \
+              'but could not select it from options without creating a new tag.'
+      end
+    elsif create_new
+      # The first item returned is not the tag we were looking for (another tag partial match + not existing)
+      # If we are allowed to create a tag, select the last option from the list, which is always the tag creation.
+      last_option = find('#select2-post_tags_cache-results li:last-child')
+      if last_option.first('span').text == tag_name
+        last_option.click
+      else
+        raise "Tried to select tag #{tag_name} for creation, but it does not seem to be a presented option."
+      end
+    else
+      # The first item returned is not the tag we were looking for, and we are not allowed to create a tag.
+      raise "Expected to find tag with the name #{tag_name}, " \
+            'but could not select it from options without creating a new tag.'
+    end
+  end
 end
diff --git a/test/fixtures/posts.yml b/test/fixtures/posts.yml
index afbab5147a4926a388b3406737a43789e49e51fd..e67b996fbea5935fa33cf500ba23f124515d3572 100644
--- a/test/fixtures/posts.yml
+++ b/test/fixtures/posts.yml
@@ -1,8 +1,8 @@
 question_one:
   post_type: question
-  title: Q1 ABCDEF GHIJKL MNOPQR STUVWX YZ
-  body: ABCDEF GHIJKL MNOPQR STUVWX YZ ABCDEF GHIJKL MNOPQR STUVWX YZ
-  body_markdown: ABCDEF GHIJKL MNOPQR STUVWX YZ ABCDEF GHIJKL MNOPQR STUVWX YZ
+  title: Q1 - This is test question number one
+  body: This is the body of test question one. Note that we did not include any markdown or HTML in here.
+  body_markdown: This is the body of test question one. Note that we did not include any markdown or HTML in here.
   tags_cache:
     - discussion
     - support
@@ -21,9 +21,9 @@ question_one:
 
 question_two:
   post_type: question
-  title: Q2 ABCDEF GHIJKL MNOPQR STUVWX YZ
-  body: ABCDEF GHIJKL MNOPQR STUVWX YZ ABCDEF GHIJKL MNOPQR STUVWX YZ
-  body_markdown: ZY XWVUTS RQPONM LKJIHG FEDCBA ZY XWVUTS RQPONM LKJIHG FEDCBA
+  title: Q2 - This is test question number two
+  body: This is the body of test question two. Note that we did not include any markdown or HTML in here.
+  body_markdown: This is the body of test question two. Note that we did not include any markdown or HTML in here.
   tags_cache:
     - discussion
     - support
@@ -42,9 +42,9 @@ question_two:
 
 bad_answers:
   post_type: question
-  title: Q1 ABCDEF GHIJKL MNOPQR STUVWX YZ
-  body: ABCDEF GHIJKL MNOPQR STUVWX YZ ABCDEF GHIJKL MNOPQR STUVWX YZ
-  body_markdown: ABCDEF GHIJKL MNOPQR STUVWX YZ ABCDEF GHIJKL MNOPQR STUVWX YZ
+  title: Q3B - This question has bad answers
+  body: Q3B - This question is in the category main, posted by standard_user. It has bad answers.
+  body_markdown: Q3B - This question is in the category main, posted by standard_user. It has bad answers.
   tags_cache:
     - discussion
     - support
@@ -63,9 +63,9 @@ bad_answers:
 
 deleted:
   post_type: question
-  title: Q3D ZY XWVUTS RQPONM LKJIHG FEDCBA
-  body: ZY XWVUTS RQPONM LKJIHG FEDCBA ZY XWVUTS RQPONM LKJIHG FEDCBA
-  body_markdown: ZY XWVUTS RQPONM LKJIHG FEDCBA ZY XWVUTS RQPONM LKJIHG FEDCBA
+  title: Q3D - This question is deleted
+  body: Q3D - This question is in the category main, posted by standard_user. It has been deleted by deleter.
+  body_markdown: Q3D - This question is in the category main, posted by standard_user. It has been deleted by deleter.
   tags_cache:
     - discussion
     - support
@@ -87,9 +87,9 @@ deleted:
 
 deleted_mod:
   post_type: question
-  title: Q3D ZY XWVUTS RQPONM LKJIHG FEDCBA
-  body: ZY XWVUTS RQPONM LKJIHG FEDCBA ZY XWVUTS RQPONM LKJIHG FEDCBA
-  body_markdown: ZY XWVUTS RQPONM LKJIHG FEDCBA ZY XWVUTS RQPONM LKJIHG FEDCBA
+  title: Q3DM - This question is deleted by a moderator
+  body: Q3DM - This question is in category main, posted by standard_user. It has been deleted by moderator.
+  body_markdown: Q3DM - This question is in category main, posted by standard_user. It has been deleted by moderator.
   tags_cache:
     - discussion
     - support
@@ -111,9 +111,9 @@ deleted_mod:
 
 closed:
   post_type: question
-  title: Q4C ABCDEF GHIJKL MNOPQR STUVWX YZ
-  body: ABCDEF GHIJKL MNOPQR STUVWX YZ ABCDEF GHIJKL MNOPQR STUVWX YZ
-  body_markdown: ZY XWVUTS RQPONM LKJIHG FEDCBA ZY XWVUTS RQPONM LKJIHG FEDCBA
+  title: Q4C - This question is closed
+  body: Q4C - This question is in the category main, posted by standard_user. It has been closed by closer.
+  body_markdown: Q4C - This question is in the category main, posted by standard_user. It has been closed by closer.
   tags_cache:
     - discussion
     - support
@@ -135,9 +135,9 @@ closed:
 
 locked:
   post_type: question
-  title: Q4C ABCDEF GHIJKL MNOPQR STUVWX YZ
-  body: ABCDEF GHIJKL MNOPQR STUVWX YZ ABCDEF GHIJKL MNOPQR STUVWX YZ
-  body_markdown: ZY XWVUTS RQPONM LKJIHG FEDCBA ZY XWVUTS RQPONM LKJIHG FEDCBA
+  title: Q4L - This question is locked
+  body: Q4L - This question is in the category main, posted by standard_user. It has been locked by deleter.
+  body_markdown: Q4L - This question is in the category main, posted by standard_user. It has been locked by deleter.
   tags_cache:
     - discussion
     - support
@@ -160,9 +160,9 @@ locked:
 
 locked_mod:
   post_type: question
-  title: LM ABCDEF GHIJKL MNOPQR STUVWX YZ
-  body: ABCDEF GHIJKL MNOPQR STUVWX YZ ABCDEF GHIJKL MNOPQR STUVWX YZ
-  body_markdown: ZY XWVUTS RQPONM LKJIHG FEDCBA ZY XWVUTS RQPONM LKJIHG FEDCBA
+  title: Q4LM - This question is locked by a moderator
+  body: Q4LM - This question is in the category main, posted by standard_user. It has been locked by moderator.
+  body_markdown: Q4LM - This question is in the category main, posted by standard_user. It has been locked by moderator.
   tags_cache:
     - discussion
     - support
@@ -185,9 +185,9 @@ locked_mod:
 
 free_edit:
   post_type: free_edit
-  title: FE ABCDEF GHIJKL MNOPQR STUVWX YZ
-  body: ABCDEF GHIJKL MNOPQR STUVWX YZ ABCDEF GHIJKL MNOPQR STUVWX YZ
-  body_markdown: ABCDEF GHIJKL MNOPQR STUVWX YZ ABCDEF GHIJKL MNOPQR STUVWX YZ
+  title: FE - This is a post of the type free_edit
+  body: FE - This is a free edit post, in the category main, posted by moderator.
+  body_markdown: FE - This is a free edit post, in the category main, posted by moderator.
   tags_cache:
     - discussion
     - support
@@ -206,9 +206,9 @@ free_edit:
 
 high_trust:
   post_type: question
-  title: Q1 ABCDEF GHIJKL MNOPQR STUVWX YZ
-  body: ABCDEF GHIJKL MNOPQR STUVWX YZ ABCDEF GHIJKL MNOPQR STUVWX YZ
-  body_markdown: ABCDEF GHIJKL MNOPQR STUVWX YZ ABCDEF GHIJKL MNOPQR STUVWX YZ
+  title: Q5 - This is a question with high trust
+  body: Q5 - By not providing any information, you know you can trust everything said in this post.
+  body_markdown: Q5 - By not providing any information, you know you can trust everything said in this post.
   tags_cache:
     - discussion
     - support
@@ -227,9 +227,8 @@ high_trust:
 
 answer_one:
   post_type: answer
-  title: A1 ABCDEF GHIJKL MNOPQR
-  body: A1 ABCDEF GHIJKL MNOPQR STUVWX YZ ABCDEF GHIJKL MNOPQR STUVWX YZ
-  body_markdown: ZY XWVUTS RQPONM LKJIHG FEDCBA ZY XWVUTS RQPONM LKJIHG FEDCBA
+  body: A1 - This is the first answer to question number 1 (Q1). It was posted by standard user.
+  body_markdown: A1 - This is the first answer to question number 1 (Q1). It was posted by standard user.
   score: 0.5
   parent: question_one
   user: standard_user
@@ -241,9 +240,8 @@ answer_one:
 
 answer_two:
   post_type: answer
-  title: A2 ABCDEF GHIJKL MNOPQR
-  body: A2 ABCDEF GHIJKL MNOPQR STUVWX YZ ABCDEF GHIJKL MNOPQR STUVWX YZ
-  body_markdown: ZY XWVUTS RQPONM LKJIHG FEDCBA ZY XWVUTS RQPONM LKJIHG FEDCBA
+  body: A2 - This is the second answer to question number 1 (Q1). It was posted by editor.
+  body_markdown: A2 - This is the second answer to question number 1 (Q1). It was posted by editor.
   score: 0.5
   parent: question_one
   user: editor
@@ -255,9 +253,8 @@ answer_two:
 
 comments_disabled:
   post_type: answer
-  title: A2 ABCDEF GHIJKL MNOPQR
-  body: A2 ABCDEF GHIJKL MNOPQR STUVWX YZ ABCDEF GHIJKL MNOPQR STUVWX YZ
-  body_markdown: ZY XWVUTS RQPONM LKJIHG FEDCBA ZY XWVUTS RQPONM LKJIHG FEDCBA
+  body: A3CD - This is the third answer to question number 1 (Q1). It has comments disabled. Posted by closer.
+  body_markdown: A3 - This is the third answer to question number 1 (Q1). It has comments disabled. Posted by closer.
   score: 0.5
   parent: question_one
   user: closer
@@ -270,9 +267,8 @@ comments_disabled:
 
 bad_answer:
   post_type: answer
-  title: A2 ABCDEF GHIJKL MNOPQR
-  body: A2 ABCDEF GHIJKL MNOPQR STUVWX YZ ABCDEF GHIJKL MNOPQR STUVWX YZ
-  body_markdown: ZY XWVUTS RQPONM LKJIHG FEDCBA ZY XWVUTS RQPONM LKJIHG FEDCBA
+  body: A3BA - This is the fourth answer to question number 1 (Q1). It has a bad score. Posted by editor.
+  body_markdown: A4 - This is the fourth answer to question number 1 (Q1). It has a bad score. Posted by editor.
   score: 0.4
   parent: question_one
   user: editor
@@ -284,9 +280,8 @@ bad_answer:
 
 really_old_answer:
   post_type: answer
-  title: A3RO ABCDEF GHIJKL MNOPQR
-  body: A3RO ABCDEF GHIJKL MNOPQR STUVWX YZ ABCDEF GHIJKL MNOPQR STUVWX YZ
-  body_markdown: ZY XWVUTS RQPONM LKJIHG FEDCBA ZY XWVUTS RQPONM LKJIHG FEDCBA
+  body: A3RO - This is the fifth answer to question number 1 (Q1). It is very old. Posted by standard_user.
+  body_markdown: A3RO - This is the fifth answer to question number 1 (Q1). It is very old. Posted by standard_user.
   score: 2
   parent: question_one
   user: standard_user
@@ -299,9 +294,8 @@ really_old_answer:
 
 deleted_answer:
   post_type: answer
-  title: A4D ABCDEF GHIJKL MNOPQR
-  body: A4D ABCDEF GHIJKL MNOPQR STUVWX YZ ABCDEF GHIJKL MNOPQR STUVWX YZ
-  body_markdown: ZY XWVUTS RQPONM LKJIHG FEDCBA ZY XWVUTS RQPONM LKJIHG FEDCBA
+  body: A4D - This is the sixth answer to question number 1 (Q1). It is deleted. Posted by closer.
+  body_markdown: A4D - This is the sixth answer to question number 1 (Q1). It is deleted. Posted by closer.
   score: 0.5
   parent: question_one
   user: closer
@@ -316,8 +310,8 @@ deleted_answer:
 
 policy_doc:
   post_type: policy_doc
-  body: PD ABCDEF GHIJKL MNOPQR STUVWX YZ ABCDEF GHIJKL MNOPQR STUVWX YZ
-  body_markdown: ZY XWVUTS RQPONM LKJIHG FEDCBA ZY XWVUTS RQPONM LKJIHG FEDCBA
+  body: PD - This is a policy document called "Terms of Service", or "tos" for short.
+  body_markdown: PD - This is a policy document called "Terms of Service", or "tos" for short.
   title: Terms of Service
   doc_slug: tos
   user: admin
@@ -336,9 +330,9 @@ help_doc:
 
 article_one:
   post_type: article
-  title: Q1 ABCDEF GHIJKL MNOPQR STUVWX YZ
-  body: ABCDEF GHIJKL MNOPQR STUVWX YZ ABCDEF GHIJKL MNOPQR STUVWX YZ
-  body_markdown: ABCDEF GHIJKL MNOPQR STUVWX YZ ABCDEF GHIJKL MNOPQR STUVWX YZ
+  title: Art1 - This is the first article
+  body: Testing is an important practice, which should probably be discussed in this test article.
+  body_markdown: Testing is an important practice, which should probably be discussed in this test article.
   tags_cache:
     - discussion
     - support
@@ -357,9 +351,9 @@ article_one:
 
 deleted_article:
   post_type: article
-  title: Q1 ABCDEF GHIJKL MNOPQR STUVWX YZ
-  body: ABCDEF GHIJKL MNOPQR STUVWX YZ ABCDEF GHIJKL MNOPQR STUVWX YZ
-  body_markdown: ABCDEF GHIJKL MNOPQR STUVWX YZ ABCDEF GHIJKL MNOPQR STUVWX YZ
+  title: Art2D - This is a deleted article
+  body: Articles are most likely deleted because they have no inherent value, just like this sentence.
+  body_markdown: Articles are most likely deleted because they have no inherent value, just like this sentence.
   tags_cache:
     - discussion
     - support
@@ -381,8 +375,8 @@ deleted_article:
 
 help_article:
   post_type: help_doc
-  body: ABCDEF GHIJKL MNOPQR STUVWX YZ ABCDEF GHIJKL MNOPQR STUVWX YZ
-  body_markdown: ABCDEF GHIJKL MNOPQR STUVWX YZ ABCDEF GHIJKL MNOPQR STUVWX YZ
+  body: HA1 - This help article is in the category Site information, with "sample" as slug.
+  body_markdown: HA1 - This help article is in the category Site information, with "sample" as slug.
   user: system
   community: sample
   help_category: Site Information
@@ -391,8 +385,8 @@ help_article:
 
 mod_help_article:
   post_type: help_doc
-  body: ABCDEF GHIJKL MNOPQR STUVWX YZ ABCDEF GHIJKL MNOPQR STUVWX YZ
-  body_markdown: ABCDEF GHIJKL MNOPQR STUVWX YZ ABCDEF GHIJKL MNOPQR STUVWX YZ
+  body: HA2 - This help article is in the category for moderators only, with "sample-mod" as slug.
+  body_markdown: HA2 - This help article is in the category for moderators only, with "sample-mod" as slug.
   user: system
   community: sample
   help_category: $Moderator
@@ -401,8 +395,8 @@ mod_help_article:
 
 disabled_help_article:
   post_type: help_doc
-  body: ABCDEF GHIJKL MNOPQR STUVWX YZ ABCDEF GHIJKL MNOPQR STUVWX YZ
-  body_markdown: ABCDEF GHIJKL MNOPQR STUVWX YZ ABCDEF GHIJKL MNOPQR STUVWX YZ
+  body: HA3 - This help article is disabled. It is in the category disabled, with "sample-disable" as slug.
+  body_markdown: HA3 - This help article is disabled. It is in the category disabled, with "sample-disable" as slug.
   user: system
   community: sample
   help_category: $Disabled
@@ -411,9 +405,9 @@ disabled_help_article:
 
 blog_post:
   post_type: blog_post
-  title: B1 ABCDEF GHIJKL MNOPQR STUVWX YZ
-  body: ABCDEF GHIJKL MNOPQR STUVWX YZ ABCDEF GHIJKL MNOPQR STUVWX YZ
-  body_markdown: ABCDEF GHIJKL MNOPQR STUVWX YZ ABCDEF GHIJKL MNOPQR STUVWX YZ
+  title: B1 - This is a blog post in the main category
+  body: B1 - This blog post is in the main category, posted by closer.
+  body_markdown: B1 - This blog post is in the main category, posted by closer.
   tags_cache:
     - discussion
     - support
diff --git a/test/system/post_test.rb b/test/system/post_test.rb
new file mode 100644
index 0000000000000000000000000000000000000000..d99cd266cab58145a3a2c59942ef050ee184fa33
--- /dev/null
+++ b/test/system/post_test.rb
@@ -0,0 +1,166 @@
+require 'application_system_test_case'
+
+class PostTest < ApplicationSystemTestCase
+  # -------------------------------------------------------
+  # Create
+  # -------------------------------------------------------
+
+  test 'Not-signed in user cannot create a post' do
+    visit root_url
+    click_on 'Create Post'
+
+    assert_current_path new_user_session_url
+  end
+
+  test 'Signed in user can create a question' do
+    category = categories(:meta)
+    log_in :standard_user
+    visit category_path(category)
+    click_on 'Create Post'
+
+    body_text = 'When running QPixel, users are generally supposed to be able to create posts. ' \
+                'Does that actually work?'
+    title_text = 'Can a signed-in user create a post?'
+
+    fill_in 'Body', with: body_text
+    fill_in 'Summarize your post with a title:', with: title_text
+    post_form_select_tag tags(:faq).name
+
+    # Check that the post is actually created
+    assert_difference 'Post.count' do
+      click_on "Save Post in #{category.name}"
+    end
+
+    # Verify that the post is correctly created
+    new_post = Post.last
+    assert_equal body_text, new_post.body_markdown
+    assert_equal title_text, new_post.title
+    assert_equal [tags(:faq)], new_post.tags
+  end
+
+  test 'Creating a question is blocked when body is too short' do
+    category = categories(:meta)
+    log_in :standard_user
+    visit category_path(category)
+    click_on 'Create Post'
+
+    fill_in 'Summarize your post with a title:', with: 'Initial title is of sufficient length'
+    post_form_select_tag tags(:faq).name
+    fill_in 'Body', with: 'Short'
+
+    # Check that the button is disabled
+    find_button "Save Post in #{category.name}", disabled: true
+
+    # After filling out body correctly, verify that the button becomes enabled
+    fill_in 'Body', with: 'This body should pass the minimum length requirements for questions in the meta category.'
+    find_button "Save Post in #{category.name}", disabled: false
+  end
+
+  test 'Creating a question is blocked when title is too short' do
+    category = categories(:meta)
+    log_in :standard_user
+    visit category_path(category)
+    click_on 'Create Post'
+
+    fill_in 'Body', with: 'This body should pass the minimum length requirements for questions in the meta category.'
+    post_form_select_tag tags(:faq).name
+    fill_in 'Summarize your post with a title:', with: 'Too short'
+
+    # Check that the button is disabled
+    find_button "Save Post in #{category.name}", disabled: true
+
+    # After filling out the title, verify that the button becomes enabled
+    fill_in 'Summarize your post with a title:', with: 'Updated title is of sufficient length'
+    find_button "Save Post in #{category.name}", disabled: false
+  end
+
+  test 'Signed in user gets to pick post type for post creation in categories with multiple types' do
+    category = categories(:main)
+    log_in :standard_user
+    visit category_path(category)
+    click_on 'Create Post'
+
+    # All the top level post types set should be present
+    category.post_types.where(is_top_level: true).each do |pt|
+      assert_link pt.name.underscore.humanize
+    end
+
+    # Pick a non-question post type
+    post_type = category.post_types.where(is_top_level: true).where.not(name: 'Question').first
+
+    # After clicking on a post type, we should be on the creation page of the correct category and post type.
+    click_on post_type.name.underscore.humanize
+    assert_current_path new_category_post_url(post_type.id, category.id)
+  end
+
+  test 'Signed in user can answer question' do
+    log_in :standard_user
+    post = posts(:question_two)
+    visit post_path(post)
+
+    # Answer the question
+    answer_text = 'You can do this by running the rails system tests, rails test:system.'
+    fill_in 'Body', with: answer_text
+    assert_difference 'Post.count' do
+      click_on "Save Post in #{post.category.name}"
+    end
+
+    # We should now be looking at our answer, look for the text on the page
+    assert_text answer_text
+
+    # The original post should also still be on the page
+    assert_text post.body
+  end
+
+  # -------------------------------------------------------
+  # Show
+  # -------------------------------------------------------
+
+  test 'Anyone can view question' do
+    post = posts(:question_one)
+    visit post_url(post)
+
+    # Check that the post is displayed somewhere on the page
+    assert_text post.title
+    assert_text post.body
+
+    # Check that answers are displayed somewhere on the page
+    assert post.children.any?, 'The post for this system test should have answers'
+    post.children.where(deleted: false).each do |child|
+      assert_text child.body
+    end
+  end
+
+  test 'Anyone can sort answers' do
+    post = posts(:question_one)
+    visit post_url(post)
+
+    click_on 'Active'
+
+    assert_current_path post_url(post, sort: 'active')
+  end
+
+  # -------------------------------------------------------
+  # Edit
+  # -------------------------------------------------------
+
+  test 'User with edit permissions can directly edit question' do
+    log_in :editor
+    post = posts(:question_two)
+    visit post_url(post)
+
+    within ".post[data-post-id=\"#{post.id}\"]" do
+      click_on 'Edit'
+    end
+
+    updated_text = 'This is the updated body text, which should be quite different from the original text!'
+    fill_in 'Body', with: updated_text
+    fill_in 'Edit Comment', with: 'Major Rewrite for Tests'
+
+    click_on "Save Post in #{post.category.name}"
+    assert_current_path post_url(post)
+
+    # Check that the page shows the updated text
+    assert_text updated_text
+  end
+end