diff --git a/packages/e2e-test-utils/CHANGELOG.md b/packages/e2e-test-utils/CHANGELOG.md
index 89d17c38b9402..b00ed1507a623 100644
--- a/packages/e2e-test-utils/CHANGELOG.md
+++ b/packages/e2e-test-utils/CHANGELOG.md
@@ -2,6 +2,12 @@
 
 ## Unreleased
 
+### Enhancement
+
+-   Changed `setOption` to use `options.php`, to allow setting any option (and to be more consistent with `getOption`). [#39502](https://github.com/WordPress/gutenberg/pull/39502)
+-   Changed `setOption` to return the changed setting's previous value (to make restoring it easier). [#39502](https://github.com/WordPress/gutenberg/pull/39502)
+-   Added a new `trashAllComments` function.
+
 ## 7.0.0 (2022-03-11)
 
 ### Breaking Changes
diff --git a/packages/e2e-test-utils/README.md b/packages/e2e-test-utils/README.md
index cf5d35bd5b972..db839113e77d2 100644
--- a/packages/e2e-test-utils/README.md
+++ b/packages/e2e-test-utils/README.md
@@ -756,6 +756,10 @@ _Parameters_
 -   _setting_ `string`: The option, used to get the option by id.
 -   _value_ `string`: The value to set the option to.
 
+_Returns_
+
+-   `string`: The previous value of the option.
+
 ### setPostContent
 
 Sets code editor content
@@ -868,6 +872,14 @@ _Parameters_
 
 -   _name_ `string`: Block name.
 
+### trashAllComments
+
+Navigates to the comments listing screen and bulk-trashes any comments which exist.
+
+_Returns_
+
+-   `Promise`: Promise resolving once comments have been trashed.
+
 ### trashAllPosts
 
 Navigates to the post listing screen and bulk-trashes any posts which exist.
diff --git a/packages/e2e-test-utils/src/index.js b/packages/e2e-test-utils/src/index.js
index 82733db4e6720..0ae7d37951669 100644
--- a/packages/e2e-test-utils/src/index.js
+++ b/packages/e2e-test-utils/src/index.js
@@ -87,6 +87,7 @@ export { toggleMoreMenu } from './toggle-more-menu';
 export { toggleOfflineMode, isOfflineMode } from './offline-mode';
 export { togglePreferencesOption } from './toggle-preferences-option';
 export { transformBlockTo } from './transform-block-to';
+export { trashAllComments } from './trash-all-comments';
 export { uninstallPlugin } from './uninstall-plugin';
 export { visitAdminPage } from './visit-admin-page';
 export { waitForWindowDimensions } from './wait-for-window-dimensions';
diff --git a/packages/e2e-test-utils/src/set-option.js b/packages/e2e-test-utils/src/set-option.js
index 85c160f51f272..90b3923a323e2 100644
--- a/packages/e2e-test-utils/src/set-option.js
+++ b/packages/e2e-test-utils/src/set-option.js
@@ -11,19 +11,27 @@ import { pressKeyWithModifier } from './press-key-with-modifier';
  *
  * @param {string} setting The option, used to get the option by id.
  * @param {string} value   The value to set the option to.
+ *
+ * @return {string} The previous value of the option.
+ *
  */
 export async function setOption( setting, value ) {
 	await switchUserToAdmin();
-	await visitAdminPage( 'options-general.php' );
+	await visitAdminPage( 'options.php' );
+
+	const previousValue = await page.$eval(
+		`#${ setting }`,
+		( element ) => element.value
+	);
 
 	await page.focus( `#${ setting }` );
 	await pressKeyWithModifier( 'primary', 'a' );
 	await page.type( `#${ setting }`, value );
 
 	await Promise.all( [
-		page.click( '#submit' ),
+		page.click( '#Update' ),
 		page.waitForNavigation( { waitUntil: 'networkidle0' } ),
 	] );
-
 	await switchUserToTest();
+	return previousValue;
 }
diff --git a/packages/e2e-test-utils/src/trash-all-comments.js b/packages/e2e-test-utils/src/trash-all-comments.js
new file mode 100644
index 0000000000000..97169e994d3ed
--- /dev/null
+++ b/packages/e2e-test-utils/src/trash-all-comments.js
@@ -0,0 +1,35 @@
+/**
+ * Internal dependencies
+ */
+import { switchUserToAdmin } from './switch-user-to-admin';
+import { switchUserToTest } from './switch-user-to-test';
+import { visitAdminPage } from './visit-admin-page';
+
+/**
+ * Navigates to the comments listing screen and bulk-trashes any comments which exist.
+ *
+ * @return {Promise} Promise resolving once comments have been trashed.
+ */
+export async function trashAllComments() {
+	await switchUserToAdmin();
+	// Visit `/wp-admin/edit-comments.php` so we can see a list of comments and delete them.
+	await visitAdminPage( 'edit-comments.php' );
+
+	// If this selector doesn't exist there are no comments for us to delete.
+	const bulkSelector = await page.$( '#bulk-action-selector-top' );
+	if ( ! bulkSelector ) {
+		return;
+	}
+
+	// Select all comments.
+	await page.waitForSelector( '[id^=cb-select-all-]' );
+	await page.click( '[id^=cb-select-all-]' );
+	// Select the "bulk actions" > "trash" option.
+	await page.select( '#bulk-action-selector-top', 'trash' );
+	// Submit the form to send all mine/pendings/approved/spam comments to the trash.
+	await page.click( '#doaction' );
+	await page.waitForXPath(
+		'//*[contains(@class, "updated notice")]/p[contains(text(), "moved to the Trash.")]'
+	);
+	await switchUserToTest();
+}
diff --git a/packages/e2e-tests/specs/experiments/blocks/comments-query.test.js b/packages/e2e-tests/specs/experiments/blocks/comments-query.test.js
new file mode 100644
index 0000000000000..55c6b4b07af96
--- /dev/null
+++ b/packages/e2e-tests/specs/experiments/blocks/comments-query.test.js
@@ -0,0 +1,93 @@
+/**
+ * WordPress dependencies
+ */
+import {
+	activateTheme,
+	createNewPost,
+	insertBlock,
+	pressKeyTimes,
+	publishPost,
+	setOption,
+	trashAllComments,
+} from '@wordpress/e2e-test-utils';
+
+describe( 'Comment Query Loop', () => {
+	let previousPageComments,
+		previousCommentsPerPage,
+		previousDefaultCommentsPage;
+	beforeAll( async () => {
+		await activateTheme( 'emptytheme' );
+		previousPageComments = await setOption( 'page_comments', '1' );
+		previousCommentsPerPage = await setOption( 'comments_per_page', '1' );
+		previousDefaultCommentsPage = await setOption(
+			'default_comments_page',
+			'newest'
+		);
+	} );
+	beforeEach( async () => {
+		await createNewPost();
+	} );
+	it( 'Pagination links are working as expected', async () => {
+		// Insert the Query Comment Loop block.
+		await insertBlock( 'Comments Query Loop' );
+		// Insert the Comment Loop form.
+		await insertBlock( 'Post Comments Form' );
+		await publishPost();
+		// Visit the post that was just published.
+		await page.click(
+			'.post-publish-panel__postpublish-buttons .is-primary'
+		);
+
+		// TODO: We can extract this into a util once we find we need it elsewhere.
+		// Create three comments for that post.
+		for ( let i = 0; i < 3; i++ ) {
+			await page.waitForSelector( 'textarea#comment' );
+			await page.click( 'textarea#comment' );
+			await page.type(
+				`textarea#comment`,
+				`This is an automated comment - ${ i }`
+			);
+			await pressKeyTimes( 'Tab', 1 );
+			await page.keyboard.press( 'Enter' );
+			await page.waitForNavigation();
+		}
+
+		// We check that there is a previous comments page link.
+		await page.waitForSelector( '.wp-block-comments-pagination-previous' );
+		expect(
+			await page.$( '.wp-block-comments-pagination-previous' )
+		).not.toBeNull();
+		expect(
+			await page.$( '.wp-block-comments-pagination-next' )
+		).toBeNull();
+
+		await page.click( '.wp-block-comments-pagination-previous' );
+
+		// We check that there are a previous and a next link.
+		await page.waitForSelector( '.wp-block-comments-pagination-previous' );
+		await page.waitForSelector( '.wp-block-comments-pagination-next' );
+		expect(
+			await page.$( '.wp-block-comments-pagination-previous' )
+		).not.toBeNull();
+		expect(
+			await page.$( '.wp-block-comments-pagination-next' )
+		).not.toBeNull();
+		await page.click( '.wp-block-comments-pagination-previous' );
+
+		// We check that there is only have a next link
+		await page.waitForSelector( '.wp-block-comments-pagination-next' );
+		expect(
+			await page.$( '.wp-block-comments-pagination-previous' )
+		).toBeNull();
+		expect(
+			await page.$( '.wp-block-comments-pagination-next' )
+		).not.toBeNull();
+	} );
+	afterAll( async () => {
+		await trashAllComments();
+		await activateTheme( 'twentytwentyone' );
+		await setOption( 'page_comments', previousPageComments );
+		await setOption( 'comments_per_page', previousCommentsPerPage );
+		await setOption( 'default_comments_page', previousDefaultCommentsPage );
+	} );
+} );
diff --git a/phpunit/class-block-library-comment-template-test.php b/phpunit/class-block-library-comment-template-test.php
index 8cc3f7465c31b..eab3b034e7709 100644
--- a/phpunit/class-block-library-comment-template-test.php
+++ b/phpunit/class-block-library-comment-template-test.php
@@ -163,4 +163,39 @@ function test_rendering_comment_template_nested() {
 			'<ol ><li><div class="wp-block-comment-author-name">Test</div><div class="wp-block-comment-content">Hello world</div><ol><li><div class="wp-block-comment-author-name">Test</div><div class="wp-block-comment-content">Hello world</div><ol><li><div class="wp-block-comment-author-name">Test</div><div class="wp-block-comment-content">Hello world</div></li></ol></li></ol></li></ol>'
 		);
 	}
+	/**
+	 * Test that both "Older Comments" and "Newer Comments" are displayed in the correct order
+	 * inside the Comment Query Loop when we enable pagination on Discussion Settings.
+	 * In order to do that, it should exist a query var 'cpage' set with the $comment_args['paged'] value.
+	 */
+	function test_build_comment_query_vars_from_block_sets_cpage_var() {
+
+		// This could be any number, we set a fixed one instead of a random for better performance.
+		$comment_query_max_num_pages = 5;
+		// We substract 1 because we created 1 comment at the beggining.
+		$post_comments_numbers = ( self::$per_page * $comment_query_max_num_pages ) - 1;
+		self::factory()->comment->create_post_comments(
+			self::$custom_post->ID,
+			$post_comments_numbers,
+			array(
+				'comment_author'       => 'Test',
+				'comment_author_email' => 'test@example.org',
+				'comment_content'      => 'Hello world',
+			)
+		);
+		$parsed_blocks = parse_blocks(
+			'<!-- wp:comment-template --><!-- wp:comment-author-name /--><!-- wp:comment-content /--><!-- /wp:comment-template -->'
+		);
+
+		$block  = new WP_Block(
+			$parsed_blocks[0],
+			array(
+				'postId'           => self::$custom_post->ID,
+				'comments/inherit' => true,
+			)
+		);
+		$actual = build_comment_query_vars_from_block( $block );
+		$this->assertEquals( $actual['paged'], $comment_query_max_num_pages );
+		$this->assertEquals( get_query_var( 'cpage' ), $comment_query_max_num_pages );
+	}
 }