diff --git a/src/material/config.bzl b/src/material/config.bzl
index d266e25bf9a7..f01b943322df 100644
--- a/src/material/config.bzl
+++ b/src/material/config.bzl
@@ -30,6 +30,7 @@ entryPoints = [
"menu",
"menu/testing",
"paginator",
+ "paginator/testing",
"progress-bar",
"progress-bar/testing",
"progress-spinner",
diff --git a/src/material/paginator/paginator.html b/src/material/paginator/paginator.html
index 64d800e157ae..4276c8067ebb 100644
--- a/src/material/paginator/paginator.html
+++ b/src/material/paginator/paginator.html
@@ -20,7 +20,9 @@
-
diff --git a/src/material/paginator/testing/BUILD.bazel b/src/material/paginator/testing/BUILD.bazel
new file mode 100644
index 000000000000..e848d5a0929f
--- /dev/null
+++ b/src/material/paginator/testing/BUILD.bazel
@@ -0,0 +1,53 @@
+package(default_visibility = ["//visibility:public"])
+
+load("//tools:defaults.bzl", "ng_test_library", "ng_web_test_suite", "ts_library")
+
+ts_library(
+ name = "testing",
+ srcs = glob(
+ ["**/*.ts"],
+ exclude = ["**/*.spec.ts"],
+ ),
+ module_name = "@angular/material/paginator/testing",
+ deps = [
+ "//src/cdk/coercion",
+ "//src/cdk/testing",
+ "//src/material/select/testing",
+ ],
+)
+
+filegroup(
+ name = "source-files",
+ srcs = glob(["**/*.ts"]),
+)
+
+ng_test_library(
+ name = "harness_tests_lib",
+ srcs = ["shared.spec.ts"],
+ deps = [
+ ":testing",
+ "//src/cdk/testing",
+ "//src/cdk/testing/private",
+ "//src/cdk/testing/testbed",
+ "//src/material/paginator",
+ "@npm//@angular/platform-browser",
+ ],
+)
+
+ng_test_library(
+ name = "unit_tests_lib",
+ srcs = glob(
+ ["**/*.spec.ts"],
+ exclude = ["shared.spec.ts"],
+ ),
+ deps = [
+ ":harness_tests_lib",
+ ":testing",
+ "//src/material/paginator",
+ ],
+)
+
+ng_web_test_suite(
+ name = "unit_tests",
+ deps = [":unit_tests_lib"],
+)
diff --git a/src/material/paginator/testing/index.ts b/src/material/paginator/testing/index.ts
new file mode 100644
index 000000000000..676ca90f1ffa
--- /dev/null
+++ b/src/material/paginator/testing/index.ts
@@ -0,0 +1,9 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+
+export * from './public-api';
diff --git a/src/material/paginator/testing/paginator-harness-filters.ts b/src/material/paginator/testing/paginator-harness-filters.ts
new file mode 100644
index 000000000000..6d5ad9dcdb7a
--- /dev/null
+++ b/src/material/paginator/testing/paginator-harness-filters.ts
@@ -0,0 +1,13 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+
+import {BaseHarnessFilters} from '@angular/cdk/testing';
+
+/** A set of criteria that can be used to filter a list of `MatPaginatorHarness` instances. */
+export interface PaginatorHarnessFilters extends BaseHarnessFilters {
+}
diff --git a/src/material/paginator/testing/paginator-harness.spec.ts b/src/material/paginator/testing/paginator-harness.spec.ts
new file mode 100644
index 000000000000..f35004bd9896
--- /dev/null
+++ b/src/material/paginator/testing/paginator-harness.spec.ts
@@ -0,0 +1,7 @@
+import {MatPaginatorModule} from '@angular/material/paginator';
+import {runHarnessTests} from './shared.spec';
+import {MatPaginatorHarness} from './paginator-harness';
+
+describe('Non-MDC-based MatPaginatorHarness', () => {
+ runHarnessTests(MatPaginatorModule, MatPaginatorHarness);
+});
diff --git a/src/material/paginator/testing/paginator-harness.ts b/src/material/paginator/testing/paginator-harness.ts
new file mode 100644
index 000000000000..ff68876b8848
--- /dev/null
+++ b/src/material/paginator/testing/paginator-harness.ts
@@ -0,0 +1,103 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+
+import {ComponentHarness, HarnessPredicate} from '@angular/cdk/testing';
+import {MatSelectHarness} from '@angular/material/select/testing';
+import {coerceNumberProperty} from '@angular/cdk/coercion';
+import {PaginatorHarnessFilters} from './paginator-harness-filters';
+
+
+/** Harness for interacting with a standard mat-paginator in tests. */
+export class MatPaginatorHarness extends ComponentHarness {
+ /** Selector used to find paginator instances. */
+ static hostSelector = '.mat-paginator';
+ private _nextButton = this.locatorFor('.mat-paginator-navigation-next');
+ private _previousButton = this.locatorFor('.mat-paginator-navigation-previous');
+ private _firstPageButton = this.locatorForOptional('.mat-paginator-navigation-first');
+ private _lastPageButton = this.locatorForOptional('.mat-paginator-navigation-last');
+ private _select = this.locatorForOptional(MatSelectHarness.with({
+ ancestor: '.mat-paginator-page-size'
+ }));
+ private _pageSizeFallback = this.locatorFor('.mat-paginator-page-size-value');
+ private _rangeLabel = this.locatorFor('.mat-paginator-range-label');
+
+ /**
+ * Gets a `HarnessPredicate` that can be used to search for a `MatPaginatorHarness` that meets
+ * certain criteria.
+ * @param options Options for filtering which paginator instances are considered a match.
+ * @return a `HarnessPredicate` configured with the given options.
+ */
+ static with(options: PaginatorHarnessFilters = {}): HarnessPredicate
{
+ return new HarnessPredicate(MatPaginatorHarness, options);
+ }
+
+ /** Goes to the next page in the paginator. */
+ async goToNextPage(): Promise {
+ return (await this._nextButton()).click();
+ }
+
+ /** Goes to the previous page in the paginator. */
+ async goToPreviousPage(): Promise {
+ return (await this._previousButton()).click();
+ }
+
+ /** Goes to the first page in the paginator. */
+ async goToFirstPage(): Promise {
+ const button = await this._firstPageButton();
+
+ // The first page button isn't enabled by default so we need to check for it.
+ if (!button) {
+ throw Error('Could not find first page button inside paginator. ' +
+ 'Make sure that `showFirstLastButtons` is enabled.');
+ }
+
+ return button.click();
+ }
+
+ /** Goes to the last page in the paginator. */
+ async goToLastPage(): Promise {
+ const button = await this._lastPageButton();
+
+ // The last page button isn't enabled by default so we need to check for it.
+ if (!button) {
+ throw Error('Could not find last page button inside paginator. ' +
+ 'Make sure that `showFirstLastButtons` is enabled.');
+ }
+
+ return button.click();
+ }
+
+ /**
+ * Sets the page size of the paginator.
+ * @param size Page size that should be select.
+ */
+ async setPageSize(size: number): Promise {
+ const select = await this._select();
+
+ // The select is only available if the `pageSizeOptions` are
+ // set to an array with more than one item.
+ if (!select) {
+ throw Error('Cannot find page size selector in paginator. ' +
+ 'Make sure that the `pageSizeOptions` have been configured.');
+ }
+
+ return select.clickOptions({text: `${size}`});
+ }
+
+ /** Gets the page size of the paginator. */
+ async getPageSize(): Promise {
+ const select = await this._select();
+ const value = select ? select.getValueText() : (await this._pageSizeFallback()).text();
+ return coerceNumberProperty(await value);
+ }
+
+ /** Gets the text of the range labe of the paginator. */
+ async getRangeLabel(): Promise {
+ return (await this._rangeLabel()).text();
+ }
+}
diff --git a/src/material/paginator/testing/public-api.ts b/src/material/paginator/testing/public-api.ts
new file mode 100644
index 000000000000..04f54e17331a
--- /dev/null
+++ b/src/material/paginator/testing/public-api.ts
@@ -0,0 +1,10 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+
+export * from './paginator-harness';
+export * from './paginator-harness-filters';
diff --git a/src/material/paginator/testing/shared.spec.ts b/src/material/paginator/testing/shared.spec.ts
new file mode 100644
index 000000000000..b4118160e29c
--- /dev/null
+++ b/src/material/paginator/testing/shared.spec.ts
@@ -0,0 +1,145 @@
+import {HarnessLoader} from '@angular/cdk/testing';
+import {TestbedHarnessEnvironment} from '@angular/cdk/testing/testbed';
+import {Component, ViewChild} from '@angular/core';
+import {NoopAnimationsModule} from '@angular/platform-browser/animations';
+import {ComponentFixture, TestBed} from '@angular/core/testing';
+import {MatPaginatorModule, PageEvent, MatPaginator} from '@angular/material/paginator';
+import {MatPaginatorHarness} from './paginator-harness';
+import {expectAsyncError} from '@angular/cdk/testing/private';
+
+/** Shared tests to run on both the original and MDC-based paginator. */
+export function runHarnessTests(
+ paginatorModule: typeof MatPaginatorModule, paginatorHarness: typeof MatPaginatorHarness) {
+ let fixture: ComponentFixture;
+ let loader: HarnessLoader;
+ let instance: PaginatorHarnessTest;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [paginatorModule, NoopAnimationsModule],
+ declarations: [PaginatorHarnessTest],
+ }).compileComponents();
+
+ fixture = TestBed.createComponent(PaginatorHarnessTest);
+ fixture.detectChanges();
+ loader = TestbedHarnessEnvironment.loader(fixture);
+ instance = fixture.componentInstance;
+ });
+
+ it('should load all paginator harnesses', async () => {
+ const paginators = await loader.getAllHarnesses(paginatorHarness);
+ expect(paginators.length).toBe(1);
+ });
+
+ it('should be able to go to the next page', async () => {
+ const paginator = await loader.getHarness(paginatorHarness);
+
+ expect(instance.pageIndex).toBe(0);
+ await paginator.goToNextPage();
+ expect(instance.pageIndex).toBe(1);
+ });
+
+ it('should be able to go to the previous page', async () => {
+ const paginator = await loader.getHarness(paginatorHarness);
+
+ instance.pageIndex = 5;
+ fixture.detectChanges();
+
+ await paginator.goToPreviousPage();
+ expect(instance.pageIndex).toBe(4);
+ });
+
+ it('should be able to go to the first page', async () => {
+ const paginator = await loader.getHarness(paginatorHarness);
+
+ instance.pageIndex = 5;
+ fixture.detectChanges();
+
+ await paginator.goToFirstPage();
+ expect(instance.pageIndex).toBe(0);
+ });
+
+ it('should be able to go to the last page', async () => {
+ const paginator = await loader.getHarness(paginatorHarness);
+
+ expect(instance.pageIndex).toBe(0);
+ await paginator.goToLastPage();
+ expect(instance.pageIndex).toBe(49);
+ });
+
+ it('should be able to set the page size', async () => {
+ const paginator = await loader.getHarness(paginatorHarness);
+
+ expect(instance.pageSize).toBe(10);
+ await paginator.setPageSize(25);
+ expect(instance.pageSize).toBe(25);
+ });
+
+ it('should be able to get the page size', async () => {
+ const paginator = await loader.getHarness(paginatorHarness);
+ expect(await paginator.getPageSize()).toBe(10);
+ });
+
+ it('should be able to get the range label', async () => {
+ const paginator = await loader.getHarness(paginatorHarness);
+ expect(await paginator.getRangeLabel()).toBe('1 – 10 of 500');
+ });
+
+ it('should throw an error if the first page button is not available', async () => {
+ const paginator = await loader.getHarness(paginatorHarness);
+
+ instance.showFirstLastButtons = false;
+ fixture.detectChanges();
+
+ await expectAsyncError(() => paginator.goToFirstPage(),
+ /Error: Could not find first page button inside paginator/);
+ });
+
+ it('should throw an error if the last page button is not available', async () => {
+ const paginator = await loader.getHarness(paginatorHarness);
+
+ instance.showFirstLastButtons = false;
+ fixture.detectChanges();
+
+ await expectAsyncError(() => paginator.goToLastPage(),
+ /Error: Could not find last page button inside paginator/);
+ });
+
+ it('should throw an error if the page size selector is not available', async () => {
+ const paginator = await loader.getHarness(paginatorHarness);
+
+ instance.pageSizeOptions = [];
+ fixture.detectChanges();
+
+ await expectAsyncError(() => paginator.setPageSize(10),
+ /Error: Cannot find page size selector in paginator/);
+ });
+}
+
+@Component({
+ template: `
+
+
+ `
+})
+class PaginatorHarnessTest {
+ @ViewChild(MatPaginator) paginator: MatPaginator;
+ length = 500;
+ pageSize = 10;
+ pageIndex = 0;
+ pageSizeOptions = [5, 10, 25];
+ showFirstLastButtons = true;
+
+ handlePageEvent(event: PageEvent) {
+ this.length = event.length;
+ this.pageSize = event.pageSize;
+ this.pageIndex = event.pageIndex;
+ }
+}
+
diff --git a/test/karma-system-config.js b/test/karma-system-config.js
index d8359d3344e4..7c61dc446ec6 100644
--- a/test/karma-system-config.js
+++ b/test/karma-system-config.js
@@ -136,6 +136,8 @@ System.config({
'@angular/material/menu/testing': 'dist/packages/material/menu/testing/index.js',
'@angular/material/menu/testing/shared.spec': 'dist/packages/material/menu/testing/shared.spec.js',
'@angular/material/paginator': 'dist/packages/material/paginator/index.js',
+ '@angular/material/paginator/testing': 'dist/packages/material/paginator/testing/index.js',
+ '@angular/material/paginator/testing/shared.spec': 'dist/packages/material/paginator/testing/shared.spec.js',
'@angular/material/progress-bar': 'dist/packages/material/progress-bar/index.js',
'@angular/material/progress-bar/testing': 'dist/packages/material/progress-bar/testing/index.js',
'@angular/material/progress-bar/testing/shared.spec': 'dist/packages/material/progress-bar/testing/shared.spec.js',
diff --git a/tools/public_api_guard/material/paginator/testing.d.ts b/tools/public_api_guard/material/paginator/testing.d.ts
new file mode 100644
index 000000000000..3700a97bb817
--- /dev/null
+++ b/tools/public_api_guard/material/paginator/testing.d.ts
@@ -0,0 +1,14 @@
+export declare class MatPaginatorHarness extends ComponentHarness {
+ getPageSize(): Promise;
+ getRangeLabel(): Promise;
+ goToFirstPage(): Promise;
+ goToLastPage(): Promise;
+ goToNextPage(): Promise;
+ goToPreviousPage(): Promise;
+ setPageSize(size: number): Promise;
+ static hostSelector: string;
+ static with(options?: PaginatorHarnessFilters): HarnessPredicate;
+}
+
+export interface PaginatorHarnessFilters extends BaseHarnessFilters {
+}